Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2024-11-30 20:55:17 +00:00
237 changed files with 15104 additions and 8948 deletions
+1
View File
@@ -17,6 +17,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
application.registerForRemoteNotifications()
removePasscodesIfReinstalled()
prepareForLaunch()
deleteOldChatArchive()
return true
}
+2 -2
View File
@@ -845,7 +845,7 @@ final class ChatModel: ObservableObject {
}
func dismissConnReqView(_ id: String) {
if id == showingInvitation?.connId {
if id == showingInvitation?.pcc.id {
markShowingInvitationUsed()
dismissAllSheets()
}
@@ -898,7 +898,7 @@ final class ChatModel: ObservableObject {
}
struct ShowingInvitation {
var connId: String
var pcc: PendingContactConnection
var connChatUsed: Bool
}
+21 -18
View File
@@ -446,6 +446,13 @@ func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, re
throw r
}
func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction) async throws -> [MemberReaction] {
let userId = try currentUserId("apiGetReactionMemebers")
let r = await chatSendCmd(.apiGetReactionMembers(userId: userId, groupId: groupId, itemId: itemId, reaction: reaction ))
if case let .reactionMembers(_, memberReactions) = r { return memberReactions }
throw r
}
func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
if case let .chatItemsDeleted(_, items, _) = r { return items }
@@ -1061,8 +1068,8 @@ func apiRejectContactRequest(contactReqId: Int64) async throws {
throw r
}
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws {
try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange))
func apiChatRead(type: ChatType, id: Int64) async throws {
try await sendCommandOkResp(.apiChatRead(type: type, id: id))
}
func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws {
@@ -1368,15 +1375,13 @@ func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
throw r
}
func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async {
func markChatRead(_ chat: Chat) async {
do {
if chat.chatStats.unreadCount > 0 {
let minItemId = chat.chatStats.minUnreadItemId
let itemRange = (minItemId, aboveItem?.id ?? chat.chatItems.last?.id ?? minItemId)
let cInfo = chat.chatInfo
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange)
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
await MainActor.run {
withAnimation { ChatModel.shared.markChatItemsRead(cInfo, aboveItem: aboveItem) }
withAnimation { ChatModel.shared.markChatItemsRead(cInfo) }
}
}
if chat.chatStats.unreadChat {
@@ -1399,17 +1404,6 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async {
}
}
func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
do {
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id))
DispatchQueue.main.async {
ChatModel.shared.markChatItemsRead(cInfo, [cItem.id])
}
} catch {
logger.error("apiChatRead error: \(responseError(error))")
}
}
func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID]) async {
do {
try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds)
@@ -1606,6 +1600,15 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni
m.chatInitialized = true
m.currentUser = try apiGetActiveUser()
m.conditions = try getServerOperators()
if shouldImportAppSettingsDefault.get() {
do {
let appSettings = try apiGetAppSettings(settings: AppSettings.current.prepareForExport())
appSettings.importIntoApp()
shouldImportAppSettingsDefault.set(false)
} catch {
logger.error("Error while importing app settings: \(error)")
}
}
if m.currentUser == nil {
onboardingStageDefault.set(.step1_SimpleXInfo)
privacyDeliveryReceiptsSet.set(true)
@@ -45,7 +45,7 @@ struct ChatInfoToolbar: View {
}
private var contactVerifiedShield: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
(Text(Image(systemName: "checkmark.shield")) + textSpace)
.font(.caption)
.foregroundColor(theme.colors.secondary)
.baselineOffset(1)
@@ -339,7 +339,7 @@ struct ChatInfoView: View {
Text(Image(systemName: "checkmark.shield"))
.foregroundColor(theme.colors.secondary)
.font(.title2)
+ Text(" ")
+ textSpace
+ Text(contact.profile.displayName)
.font(.largeTitle)
)
@@ -47,7 +47,7 @@ struct CIFeaturePreferenceView: View {
+ Text(acceptText)
.fontWeight(.medium)
.foregroundColor(theme.colors.primary)
+ Text(" ")
+ Text(verbatim: " ")
}
r = r + chatItem.timestampText
.fontWeight(.light)
@@ -45,7 +45,7 @@ struct CIGroupInvitationView: View {
Text(chatIncognito ? "Tap to join incognito" : "Tap to join")
.foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary)
.font(.callout)
+ Text(" ")
+ Text(verbatim: " ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp)
)
.overlay(DetermineWidth())
@@ -53,7 +53,7 @@ struct CIGroupInvitationView: View {
} else {
(
groupInvitationText()
+ Text(" ")
+ Text(verbatim: " ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp)
)
.overlay(DetermineWidth())
@@ -45,7 +45,7 @@ struct CIMemberCreatedContactView: View {
+ Text(openText)
.fontWeight(.medium)
.foregroundColor(theme.colors.primary)
+ Text(" ")
+ Text(verbatim: " ")
}
r = r + chatItem.timestampText
.fontWeight(.light)
@@ -83,7 +83,7 @@ enum MetaColorMode {
? Image("checkmark.wide")
: Image(systemName: "circlebadge.fill")
).foregroundColor(.clear)
case .invertedMaterial: Text(" ").kerning(13)
case .invertedMaterial: textSpace.kerning(13)
}
}
}
@@ -120,7 +120,7 @@ func ciMetaText(
if ttl != chatTTL {
r = r + colored(Text(shortTimeText(ttl)), resolved)
}
space = Text(" ")
space = textSpace
}
if showViaProxy, meta.sentViaProxy == true {
appendSpace()
@@ -138,12 +138,12 @@ func ciMetaText(
} else if !meta.disappearing {
r = r + colorMode.statusSpacer(meta.itemStatus.sent)
}
space = Text(" ")
space = textSpace
}
if let enc = encrypted {
appendSpace()
r = r + statusIconText(enc ? "lock" : "lock.open", resolved)
space = Text(" ")
space = textSpace
}
if showTimesamp {
appendSpace()
@@ -121,11 +121,11 @@ struct CIRcvDecryptionError: View {
Text(Image(systemName: "exclamationmark.arrow.triangle.2.circlepath"))
.foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary)
.font(.callout)
+ Text(" ")
+ textSpace
+ Text("Fix connection")
.foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary)
.font(.callout)
+ Text(" ")
+ Text(verbatim: " ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp)
)
}
@@ -144,7 +144,7 @@ struct CIRcvDecryptionError: View {
Text(chatItem.content.text)
.foregroundColor(.red)
.italic()
+ Text(" ")
+ Text(verbatim: " ")
+ ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp)
}
.padding(.horizontal, 12)
@@ -17,7 +17,7 @@ struct MarkedDeletedItemView: View {
var chatItem: ChatItem
var body: some View {
(Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText)
(Text(mergedMarkedDeletedText).italic() + textSpace + chatItem.timestampText)
.font(.caption)
.foregroundColor(theme.colors.secondary)
.padding(.horizontal, 12)
@@ -11,7 +11,7 @@ import SimpleXChat
let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1)
private let noTyping = Text(" ")
private let noTyping = Text(verbatim: " ")
private let typingIndicators: [Text] = [
(typing(.black) + typing() + typing()),
@@ -85,7 +85,7 @@ struct MsgContentView: View {
}
private func reserveSpaceForMeta(_ mt: CIMeta) -> Text {
(rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp)
(rightToLeft ? Text("\n") : Text(verbatim: " ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp)
}
}
@@ -104,7 +104,7 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: St
}
if let i = icon {
res = Text(Image(systemName: i)).foregroundColor(secondaryColor) + Text(" ") + res
res = Text(Image(systemName: i)).foregroundColor(secondaryColor) + textSpace + res
}
if let s = sender {
@@ -170,7 +170,7 @@ struct ChatItemContentView<Content: View>: View {
private func eventItemViewText(_ secondaryColor: Color) -> Text {
if !revealed, let t = mergedGroupEventText {
return chatEventText(t + Text(" ") + chatItem.timestampText, secondaryColor)
return chatEventText(t + textSpace + chatItem.timestampText, secondaryColor)
} else if let member = chatItem.memberDisplayName {
return Text(member + " ")
.font(.caption)
@@ -203,7 +203,7 @@ struct ChatItemContentView<Content: View>: View {
} else if ns.count == 0 {
Text("\(count) group events")
} else if count > ns.count {
Text(members) + Text(" ") + Text("and \(count - ns.count) other events")
Text(members) + textSpace + Text("and \(count - ns.count) other events")
} else {
Text(members)
}
@@ -234,7 +234,7 @@ func chatEventText(_ text: Text, _ secondaryColor: Color) -> Text {
}
func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text, _ secondaryColor: Color) -> Text {
chatEventText(Text(eventText) + Text(" ") + ts, secondaryColor)
chatEventText(Text(eventText) + textSpace + ts, secondaryColor)
}
func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text {
+120 -11
View File
@@ -901,7 +901,7 @@ struct ChatView: View {
@State private var showChatItemInfoSheet: Bool = false
@State private var chatItemInfo: ChatItemInfo?
@State private var msgWidth: CGFloat = 0
@Binding var selectedChatItems: Set<Int64>?
@Binding var forwardedChatItems: [ChatItem]
@@ -987,7 +987,7 @@ struct ChatView: View {
}
} else if chatItem.isRcvNew {
waitToMarkRead {
await apiMarkChatItemRead(chat.chatInfo, chatItem)
await apiMarkChatItemsRead(chat.chatInfo, [chatItem.id])
}
}
}
@@ -1117,14 +1117,12 @@ struct ChatView: View {
HStack(alignment: .top, spacing: 10) {
MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background)
.onTapGesture {
if let member = m.getGroupMember(member.groupMemberId) {
selectedMember = member
if let mem = m.getGroupMember(member.groupMemberId) {
selectedMember = mem
} else {
Task {
await m.loadGroupMembers(groupInfo) {
selectedMember = m.getGroupMember(member.groupMemberId)
}
}
let mem = GMember.init(member)
m.groupMembers.append(mem)
selectedMember = mem
}
}
chatItemWithMenu(ci, range, maxWidth, itemSeparation)
@@ -1244,11 +1242,20 @@ struct ChatView: View {
}
.padding(.horizontal, 6)
.padding(.vertical, 4)
if chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted) {
.if(chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted)) { v in
v.onTapGesture {
setReaction(ci, add: !r.userReacted, reaction: r.reaction)
}
}
if case let .group(groupInfo) = chat.chatInfo {
v.contextMenu {
ReactionContextMenu(
groupInfo: groupInfo,
itemId: ci.id,
reactionCount: r,
selectedMember: $selectedMember
)
}
} else {
v
}
@@ -1838,6 +1845,108 @@ private func buildTheme() -> AppTheme {
}
}
struct ReactionContextMenu: View {
@EnvironmentObject var m: ChatModel
let groupInfo: GroupInfo
var itemId: Int64
var reactionCount: CIReactionCount
@Binding var selectedMember: GMember?
@State private var memberReactions: [MemberReaction] = []
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
var body: some View {
groupMemberReactionList()
.task {
logger.debug("ReactionContextMenu task \(radius)")
await loadChatItemReaction()
}
}
@ViewBuilder private func groupMemberReactionList() -> some View {
if memberReactions.isEmpty {
ForEach(Array(repeating: 0, count: reactionCount.totalReacted), id: \.self) { _ in
Text(verbatim: " ")
}
} else {
ForEach(memberReactions, id: \.groupMember.groupMemberId) { mr in
let mem = mr.groupMember
let userMember = mem.groupMemberId == groupInfo.membership.groupMemberId
Button {
if let member = m.getGroupMember(mem.groupMemberId) {
selectedMember = member
} else {
let member = GMember.init(mem)
m.groupMembers.append(member)
selectedMember = member
}
} label: {
HStack {
Text(mem.displayName)
if let img = cropImage(mem.image) {
Image(uiImage: img)
} else {
Image(systemName: "person.crop.circle")
}
}
}
.disabled(userMember)
}
}
}
private func cropImage(_ img: String?) -> UIImage? {
return if let originalImage = imageFromBase64(img) {
maskToCustomShape(originalImage, size: 30, radius: radius)
} else {
nil
}
}
private func loadChatItemReaction() async {
do {
let memberReactions = try await apiGetReactionMembers(
groupId: groupInfo.groupId,
itemId: itemId,
reaction: reactionCount.reaction
)
await MainActor.run {
self.memberReactions = memberReactions
}
} catch let error {
logger.error("apiGetReactionMembers error: \(responseError(error))")
}
}
}
func maskToCustomShape(_ image: UIImage, size: CGFloat, radius: CGFloat) -> UIImage {
let path = Path { path in
if radius >= 50 {
path.addEllipse(in: CGRect(x: 0, y: 0, width: size, height: size))
} else if radius <= 0 {
path.addRect(CGRect(x: 0, y: 0, width: size, height: size))
} else {
let cornerRadius = size * CGFloat(radius) / 100
path.addRoundedRect(
in: CGRect(x: 0, y: 0, width: size, height: size),
cornerSize: CGSize(width: cornerRadius, height: cornerRadius),
style: .continuous
)
}
}
return UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { context in
context.cgContext.addPath(path.cgPath)
context.cgContext.clip()
let scale = size / max(image.size.width, image.size.height)
let imageSize = CGSize(width: image.size.width * scale, height: image.size.height * scale)
let imageOrigin = CGPoint(
x: (size - imageSize.width) / 2,
y: (size - imageSize.height) / 2
)
image.draw(in: CGRect(origin: imageOrigin, size: imageSize))
}
}
struct ToggleNtfsButton: View {
@ObservedObject var chat: Chat
@@ -85,7 +85,7 @@ struct ContextItemView: View {
}
func image(_ s: String) -> Text {
Text(Image(systemName: s)).foregroundColor(Color(uiColor: .tertiaryLabel)) + Text(" ")
Text(Image(systemName: s)).foregroundColor(Color(uiColor: .tertiaryLabel)) + textSpace
}
}
}
@@ -418,7 +418,7 @@ struct GroupChatInfoView: View {
}
private var memberVerifiedShield: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
(Text(Image(systemName: "checkmark.shield")) + textSpace)
.font(.caption)
.baselineOffset(2)
.kerning(-2)
@@ -388,7 +388,7 @@ struct GroupMemberInfoView: View {
Text(Image(systemName: "checkmark.shield"))
.foregroundColor(theme.colors.secondary)
.font(.title2)
+ Text(" ")
+ textSpace
+ Text(mem.displayName)
.font(.largeTitle)
)
@@ -42,7 +42,8 @@ struct ChatHelp: View {
Text("above, then choose:")
}
Text("**Add contact**: to create a new invitation link, or connect via a link you received.")
Text("**Create 1-time link**: to create and share a new invitation link.")
Text("**Scan / Paste link**: to connect via a link you received.")
Text("**Create group**: to create a new group.")
}
.padding(.top, 24)
@@ -172,7 +172,7 @@ struct ChatPreviewView: View {
}
private var verifiedIcon: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
(Text(Image(systemName: "checkmark.shield")) + textSpace)
.foregroundColor(theme.colors.secondary)
.baselineOffset(1)
.kerning(-2)
@@ -232,12 +232,12 @@ struct ChatPreviewView: View {
+ messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary)
func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text {
Text(Image(systemName: s)).foregroundColor(color) + Text(" ")
Text(Image(systemName: s)).foregroundColor(color) + textSpace
}
func attachment() -> Text {
switch draft.preview {
case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + Text(" ")
case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + textSpace
case .mediaPreviews: return image("photo")
case let .voicePreview(_, duration): return image("play.fill") + Text(durationText(duration))
default: return Text("")
@@ -367,11 +367,11 @@ struct ChatPreviewView: View {
case .sndErrorAuth, .sndError:
return Text(Image(systemName: "multiply"))
.font(.caption)
.foregroundColor(.red) + Text(" ")
.foregroundColor(.red) + textSpace
case .sndWarning:
return Text(Image(systemName: "exclamationmark.triangle.fill"))
.font(.caption)
.foregroundColor(.orange) + Text(" ")
.foregroundColor(.orange) + textSpace
default: return Text("")
}
}
@@ -151,7 +151,7 @@ struct ContactListNavLink: View {
}
private var verifiedIcon: Text {
(Text(Image(systemName: "checkmark.shield")) + Text(" "))
(Text(Image(systemName: "checkmark.shield")) + textSpace)
.foregroundColor(.secondary)
.baselineOffset(1)
.kerning(-2)
@@ -1,68 +0,0 @@
//
// ChatArchiveView.swift
// SimpleXChat
//
// Created by Evgeny on 23/06/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct ChatArchiveView: View {
@EnvironmentObject var theme: AppTheme
var archiveName: String
@AppStorage(DEFAULT_CHAT_ARCHIVE_NAME) private var chatArchiveName: String?
@AppStorage(DEFAULT_CHAT_ARCHIVE_TIME) private var chatArchiveTime: Double = 0
@State private var showDeleteAlert = false
var body: some View {
let fileUrl = getDocumentsDirectory().appendingPathComponent(archiveName)
let fileTs = chatArchiveTimeDefault.get()
List {
Section {
settingsRow("square.and.arrow.up", color: theme.colors.secondary) {
Button {
showShareSheet(items: [fileUrl])
} label: {
Text("Save archive")
}
}
settingsRow("trash", color: theme.colors.secondary) {
Button {
showDeleteAlert = true
} label: {
Text("Delete archive").foregroundColor(.red)
}
}
} header: {
Text("Chat archive")
.foregroundColor(theme.colors.secondary)
} footer: {
Text("Created on \(fileTs)")
.foregroundColor(theme.colors.secondary)
}
}
.alert(isPresented: $showDeleteAlert) {
Alert(
title: Text("Delete chat archive?"),
primaryButton: .destructive(Text("Delete")) {
do {
try FileManager.default.removeItem(atPath: fileUrl.path)
chatArchiveName = nil
chatArchiveTime = 0
} catch let error {
logger.error("removeItem error \(String(describing: error))")
}
},
secondaryButton: .cancel()
)
}
}
}
struct ChatArchiveView_Previews: PreviewProvider {
static var previews: some View {
ChatArchiveView(archiveName: "")
}
}
@@ -48,6 +48,8 @@ struct DatabaseEncryptionView: View {
@State private var confirmNewKey = ""
@State private var currentKeyShown = false
let stopChatRunBlockStartChat: (Binding<Bool>, @escaping () async throws -> Bool) -> Void
var body: some View {
ZStack {
List {
@@ -134,46 +136,61 @@ struct DatabaseEncryptionView: View {
.onAppear {
if initialRandomDBPassphrase { currentKey = kcDatabasePassword.get() ?? "" }
}
.disabled(m.chatRunning != false)
.disabled(progressIndicator)
.alert(item: $alert) { item in databaseEncryptionAlert(item) }
}
private func encryptDatabase() {
progressIndicator = true
Task {
do {
encryptionStartedDefault.set(true)
encryptionStartedAtDefault.set(Date.now)
if !m.chatDbChanged {
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
}
try await apiStorageEncryption(currentKey: currentKey, newKey: newKey)
encryptionStartedDefault.set(false)
initialRandomDBPassphraseGroupDefault.set(false)
if migration {
storeDBPassphraseGroupDefault.set(useKeychain)
}
if useKeychain {
if kcDatabasePassword.set(newKey) {
await resetFormAfterEncryption(true)
await operationEnded(.databaseEncrypted)
} else {
await resetFormAfterEncryption()
await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain"))
}
} else {
if migration {
removePassphraseFromKeyChain()
}
await resetFormAfterEncryption()
private func encryptDatabaseAsync() async -> Bool {
await MainActor.run {
progressIndicator = true
}
do {
encryptionStartedDefault.set(true)
encryptionStartedAtDefault.set(Date.now)
if !m.chatDbChanged {
try apiSaveAppSettings(settings: AppSettings.current.prepareForExport())
}
try await apiStorageEncryption(currentKey: currentKey, newKey: newKey)
encryptionStartedDefault.set(false)
initialRandomDBPassphraseGroupDefault.set(false)
if migration {
storeDBPassphraseGroupDefault.set(useKeychain)
}
if useKeychain {
if kcDatabasePassword.set(newKey) {
await resetFormAfterEncryption(true)
await operationEnded(.databaseEncrypted)
}
} catch let error {
if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse {
await operationEnded(.currentPassphraseError)
} else {
await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))"))
await resetFormAfterEncryption()
await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain"))
}
} else {
if migration {
removePassphraseFromKeyChain()
}
await resetFormAfterEncryption()
await operationEnded(.databaseEncrypted)
}
return true
} catch let error {
if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse {
await operationEnded(.currentPassphraseError)
} else {
await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))"))
}
return false
}
}
private func encryptDatabase() {
// it will try to stop and start the chat in case of: non-migration && successful encryption. In migration the chat will remain stopped
if migration {
Task {
await encryptDatabaseAsync()
}
} else {
stopChatRunBlockStartChat($progressIndicator) {
return await encryptDatabaseAsync()
}
}
}
@@ -371,6 +388,6 @@ func validKey(_ s: String) -> Bool {
struct DatabaseEncryptionView_Previews: PreviewProvider {
static var previews: some View {
DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false)
DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false, stopChatRunBlockStartChat: { _, _ in true })
}
}
@@ -11,6 +11,7 @@ import SimpleXChat
struct DatabaseErrorView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@State var status: DBMigrationResult
@State private var dbKey = ""
@State private var storedDBKey = kcDatabasePassword.get()
@@ -28,23 +29,39 @@ struct DatabaseErrorView: View {
}
@ViewBuilder private func databaseErrorView() -> some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .center, spacing: 20) {
switch status {
case let .errorNotADatabase(dbFile):
if useKeychain && storedDBKey != nil && storedDBKey != "" {
titleText("Wrong database passphrase")
Text("Database passphrase is different from saved in the keychain.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 25)
databaseKeyField(onSubmit: saveAndRunChat)
saveAndOpenButton()
fileNameText(dbFile)
Spacer()
VStack(spacing: 10) {
saveAndOpenButton()
fileNameText(dbFile)
}
} else {
titleText("Encrypted database")
Text("Database passphrase is required to open chat.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 25)
.padding(.bottom, 5)
if useKeychain {
databaseKeyField(onSubmit: saveAndRunChat)
Spacer()
saveAndOpenButton()
} else {
databaseKeyField(onSubmit: { runChat() })
Spacer()
openChatButton()
}
}
@@ -52,73 +69,105 @@ struct DatabaseErrorView: View {
switch migrationError {
case let .upgrade(upMigrations):
titleText("Database upgrade")
Button("Upgrade and open chat") { runChat(confirmMigrations: .yesUp) }
fileNameText(dbFile)
migrationsText(upMigrations.map(\.upName))
Spacer()
VStack(spacing: 10) {
Button("Upgrade and open chat") {
runChat(confirmMigrations: .yesUp)
}.buttonStyle(OnboardingButtonStyle(isDisabled: false))
fileNameText(dbFile)
}
case let .downgrade(downMigrations):
titleText("Database downgrade")
Text("Warning: you may lose some data!").bold()
Button("Downgrade and open chat") { runChat(confirmMigrations: .yesUpDown) }
fileNameText(dbFile)
Text("Warning: you may lose some data!")
.bold()
.padding(.horizontal, 25)
.multilineTextAlignment(.center)
migrationsText(downMigrations)
Spacer()
VStack(spacing: 10) {
Button("Downgrade and open chat") {
runChat(confirmMigrations: .yesUpDown)
}.buttonStyle(OnboardingButtonStyle(isDisabled: false))
fileNameText(dbFile)
}
case let .migrationError(mtrError):
titleText("Incompatible database version")
fileNameText(dbFile)
Text("Error: ") + Text(mtrErrorDescription(mtrError))
fileNameText(dbFile, font: .callout)
errorView(Text(mtrErrorDescription(mtrError)))
}
case let .errorSQL(dbFile, migrationSQLError):
titleText("Database error")
fileNameText(dbFile)
Text("Error: \(migrationSQLError)")
fileNameText(dbFile, font: .callout)
errorView(Text("Error: \(migrationSQLError)"))
case .errorKeychain:
titleText("Keychain error")
Text("Cannot access keychain to save database password")
errorView(Text("Cannot access keychain to save database password"))
case .invalidConfirmation:
// this can only happen if incorrect parameter is passed
Text(String("Invalid migration confirmation")).font(.title)
titleText("Invalid migration confirmation")
errorView()
case let .unknown(json):
titleText("Database error")
Text("Unknown database error: \(json)")
errorView(Text("Unknown database error: \(json)"))
case .ok:
EmptyView()
}
if showRestoreDbButton {
Spacer().frame(height: 10)
Spacer()
Text("The attempt to change database passphrase was not completed.")
.multilineTextAlignment(.center)
.padding(.horizontal, 25)
.font(.footnote)
restoreDbButton()
}
}
.padding()
.padding(.horizontal, 25)
.padding(.top, 75)
.padding(.bottom, 25)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.onAppear() { showRestoreDbButton = shouldShowRestoreDbButton() }
}
private func titleText(_ s: LocalizedStringKey) -> Text {
Text(s).font(.title)
private func titleText(_ s: LocalizedStringKey) -> some View {
Text(s).font(.largeTitle).bold().multilineTextAlignment(.center)
}
private func fileNameText(_ f: String) -> Text {
Text("File: \((f as NSString).lastPathComponent)")
private func fileNameText(_ f: String, font: Font = .caption) -> Text {
Text("File: \((f as NSString).lastPathComponent)").font(font)
}
private func migrationsText(_ ms: [String]) -> Text {
Text("Migrations: \(ms.joined(separator: ", "))")
private func migrationsText(_ ms: [String]) -> some View {
(Text("Migrations:").font(.subheadline) + Text(verbatim: "\n") + Text(ms.joined(separator: "\n")).font(.caption))
.multilineTextAlignment(.center)
.padding(.horizontal, 25)
}
private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View {
PassphraseField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit)
.padding(.vertical, 10)
.padding(.horizontal)
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color(uiColor: .tertiarySystemFill))
)
}
private func saveAndOpenButton() -> some View {
Button("Save passphrase and open chat") {
saveAndRunChat()
}
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
}
private func openChatButton() -> some View {
Button("Open chat") {
runChat()
}
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
}
private func saveAndRunChat() {
@@ -192,8 +241,9 @@ struct DatabaseErrorView: View {
secondaryButton: .cancel()
))
} label: {
Text("Restore database backup").foregroundColor(.red)
Text("Restore database backup")
}
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
}
private func restoreDb() {
@@ -208,6 +258,23 @@ struct DatabaseErrorView: View {
))
}
}
private func errorView(_ s: Text? = nil) -> some View {
VStack(spacing: 35) {
Image(systemName: "exclamationmark.triangle.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.red)
if let text = s {
text
.multilineTextAlignment(.center)
.font(.footnote)
}
}
.padding()
.frame(maxWidth: .infinity)
}
}
struct DatabaseErrorView_Previews: PreviewProvider {
+230 -126
View File
@@ -46,6 +46,7 @@ struct DatabaseView: View {
@EnvironmentObject var theme: AppTheme
let dismissSettingsSheet: DismissAction
@State private var runChat = false
@State private var stoppingChat = false
@State private var alert: DatabaseAlert? = nil
@State private var showFileImporter = false
@State private var importedArchivePath: URL?
@@ -57,6 +58,8 @@ struct DatabaseView: View {
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
@State private var appFilesCountAndSize: (Int, Int)?
@State private var showDatabaseEncryptionView = false
@State var chatItemTTL: ChatItemTTL
@State private var currentChatItemTTL: ChatItemTTL = .none
@@ -69,7 +72,20 @@ struct DatabaseView: View {
}
}
@ViewBuilder
private func chatDatabaseView() -> some View {
NavigationLink(isActive: $showDatabaseEncryptionView) {
DatabaseEncryptionView(useKeychain: $useKeychain, migration: false, stopChatRunBlockStartChat: { progressIndicator, block in
stopChatRunBlockStartChat(false, progressIndicator, block)
})
.navigationTitle("Database passphrase")
.modifier(ThemedBackground(grouped: true))
} label: {
EmptyView()
}
.frame(width: 1, height: 1)
.hidden()
List {
let stopped = m.chatRunning == false
Section {
@@ -101,9 +117,10 @@ struct DatabaseView: View {
isOn: $runChat
)
.onChange(of: runChat) { _ in
if (runChat) {
startChat()
} else {
if runChat {
DatabaseView.startChat($runChat, $progressIndicator)
} else if !stoppingChat {
stoppingChat = false
alert = .stopChat
}
}
@@ -123,7 +140,9 @@ struct DatabaseView: View {
let color: Color = unencrypted ? .orange : theme.colors.secondary
settingsRow(unencrypted ? "lock.open" : useKeychain ? "key" : "lock", color: color) {
NavigationLink {
DatabaseEncryptionView(useKeychain: $useKeychain, migration: false)
DatabaseEncryptionView(useKeychain: $useKeychain, migration: false, stopChatRunBlockStartChat: { progressIndicator, block in
stopChatRunBlockStartChat(false, progressIndicator, block)
})
.navigationTitle("Database passphrase")
.modifier(ThemedBackground(grouped: true))
} label: {
@@ -133,9 +152,14 @@ struct DatabaseView: View {
settingsRow("square.and.arrow.up", color: theme.colors.secondary) {
Button("Export database") {
if initialRandomDBPassphraseGroupDefault.get() && !unencrypted {
alert = .exportProhibited
showDatabaseEncryptionView = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
alert = .exportProhibited
}
} else {
exportArchive()
stopChatRunBlockStartChat(stopped, $progressIndicator) {
await exportArchive()
}
}
}
}
@@ -144,20 +168,6 @@ struct DatabaseView: View {
showFileImporter = true
}
}
if let archiveName = chatArchiveName {
let title: LocalizedStringKey = chatArchiveTimeDefault.get() < chatLastStartGroupDefault.get()
? "Old database archive"
: "New database archive"
settingsRow("archivebox", color: theme.colors.secondary) {
NavigationLink {
ChatArchiveView(archiveName: archiveName)
.navigationTitle(title)
.modifier(ThemedBackground(grouped: true))
} label: {
Text(title)
}
}
}
settingsRow("trash.slash", color: theme.colors.secondary) {
Button("Delete database", role: .destructive) {
alert = .deleteChat
@@ -167,14 +177,10 @@ struct DatabaseView: View {
Text("Chat database")
.foregroundColor(theme.colors.secondary)
} footer: {
Text(
stopped
? "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."
: "Stop chat to enable database actions"
)
Text("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.")
.foregroundColor(theme.colors.secondary)
}
.disabled(!stopped)
.disabled(progressIndicator)
if case .group = dbContainer, legacyDatabase {
Section(header: Text("Old database").foregroundColor(theme.colors.secondary)) {
@@ -190,7 +196,7 @@ struct DatabaseView: View {
Button(m.users.count > 1 ? "Delete files for all chat profiles" : "Delete all files", role: .destructive) {
alert = .deleteFilesAndMedia
}
.disabled(!stopped || appFilesCountAndSize?.0 == 0)
.disabled(progressIndicator || appFilesCountAndSize?.0 == 0)
} header: {
Text("Files & media")
.foregroundColor(theme.colors.secondary)
@@ -255,7 +261,10 @@ struct DatabaseView: View {
title: Text("Import chat database?"),
message: Text("Your current chat database will be DELETED and REPLACED with the imported one.") + Text("This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost."),
primaryButton: .destructive(Text("Import")) {
importArchive(fileURL)
stopChatRunBlockStartChat(m.chatRunning == false, $progressIndicator) {
_ = await DatabaseView.importArchive(fileURL, $progressIndicator, $alert)
return true
}
},
secondaryButton: .cancel()
)
@@ -263,19 +272,15 @@ struct DatabaseView: View {
return Alert(title: Text("Error: no database file"))
}
case .archiveImported:
return Alert(
title: Text("Chat database imported"),
message: Text("Restart the app to use imported chat database")
)
let (title, message) = archiveImportedAlertText()
return Alert(title: Text(title), message: Text(message))
case let .archiveImportedWithErrors(errs):
return Alert(
title: Text("Chat database imported"),
message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs)
)
let (title, message) = archiveImportedWithErrorsAlertText(errs: errs)
return Alert(title: Text(title), message: Text(message))
case let .archiveExportedWithErrors(archivePath, errs):
return Alert(
title: Text("Chat database exported"),
message: Text("You may save the exported archive.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs),
message: Text("You may save the exported archive.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + Text(archiveErrorsText(errs)),
dismissButton: .default(Text("Continue")) {
showShareSheet(items: [archivePath])
}
@@ -285,15 +290,17 @@ struct DatabaseView: View {
title: Text("Delete chat profile?"),
message: Text("This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost."),
primaryButton: .destructive(Text("Delete")) {
deleteChat()
let wasStopped = m.chatRunning == false
stopChatRunBlockStartChat(wasStopped, $progressIndicator) {
_ = await deleteChat()
return true
}
},
secondaryButton: .cancel()
)
case .chatDeleted:
return Alert(
title: Text("Chat database deleted"),
message: Text("Restart the app to create a new chat profile")
)
let (title, message) = chatDeletedAlertText()
return Alert(title: Text(title), message: Text(message))
case .deleteLegacyDatabase:
return Alert(
title: Text("Delete old database?"),
@@ -308,7 +315,10 @@ struct DatabaseView: View {
title: Text("Delete files and media?"),
message: Text("This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain."),
primaryButton: .destructive(Text("Delete")) {
deleteFiles()
stopChatRunBlockStartChat(m.chatRunning == false, $progressIndicator) {
deleteFiles()
return true
}
},
secondaryButton: .cancel()
)
@@ -328,95 +338,180 @@ struct DatabaseView: View {
}
}
private func authStopChat() {
private func authStopChat(_ onStop: (() -> Void)? = nil) {
if UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) {
authenticate(reason: NSLocalizedString("Stop SimpleX", comment: "authentication reason")) { laResult in
switch laResult {
case .success: stopChat()
case .unavailable: stopChat()
case .success: stopChat(onStop)
case .unavailable: stopChat(onStop)
case .failed: withAnimation { runChat = true }
}
}
} else {
stopChat()
stopChat(onStop)
}
}
private func stopChat() {
private func stopChat(_ onStop: (() -> Void)? = nil) {
Task {
do {
try await stopChatAsync()
onStop?()
} catch let error {
await MainActor.run {
runChat = true
alert = .error(title: "Error stopping chat", error: responseError(error))
showAlert("Error stopping chat", message: responseError(error))
}
}
}
}
private func exportArchive() {
progressIndicator = true
Task {
do {
let (archivePath, archiveErrors) = try await exportChatArchive()
if archiveErrors.isEmpty {
showShareSheet(items: [archivePath])
await MainActor.run { progressIndicator = false }
} else {
await MainActor.run {
alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors)
progressIndicator = false
func stopChatRunBlockStartChat(
_ stopped: Bool,
_ progressIndicator: Binding<Bool>,
_ block: @escaping () async throws -> Bool
) {
// if the chat was running, the sequence is: stop chat, run block, start chat.
// Otherwise, just run block and do nothing - the toggle will be visible anyway and the user can start the chat or not
if stopped {
Task {
do {
_ = try await block()
} catch {
logger.error("Error while executing block: \(error)")
}
}
} else {
authStopChat {
stoppingChat = true
runChat = false
Task {
// if it throws, let's start chat again anyway
var canStart = false
do {
canStart = try await block()
} catch {
logger.error("Error executing block: \(error)")
canStart = true
}
if canStart {
await MainActor.run {
DatabaseView.startChat($runChat, $progressIndicator)
}
}
}
}
}
}
static func startChat(_ runChat: Binding<Bool>, _ progressIndicator: Binding<Bool>) {
progressIndicator.wrappedValue = true
let m = ChatModel.shared
if m.chatDbChanged {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
resetChatCtrl()
do {
let hadDatabase = hasDatabase()
try initializeChat(start: true)
m.chatDbChanged = false
AppChatState.shared.set(.active)
if m.chatDbStatus != .ok || !hadDatabase {
// Hide current view and show `DatabaseErrorView`
dismissAllSheets(animated: true)
}
} catch let error {
fatalError("Error starting chat \(responseError(error))")
}
progressIndicator.wrappedValue = false
}
} else {
do {
_ = try apiStartChat()
runChat.wrappedValue = true
m.chatRunning = true
ChatReceiver.shared.start()
chatLastStartGroupDefault.set(Date.now)
AppChatState.shared.set(.active)
} catch let error {
runChat.wrappedValue = false
showAlert(NSLocalizedString("Error starting chat", comment: ""), message: responseError(error))
}
progressIndicator.wrappedValue = false
}
}
private func exportArchive() async -> Bool {
await MainActor.run {
progressIndicator = true
}
do {
let (archivePath, archiveErrors) = try await exportChatArchive()
if archiveErrors.isEmpty {
showShareSheet(items: [archivePath])
await MainActor.run { progressIndicator = false }
} else {
await MainActor.run {
alert = .error(title: "Error exporting chat database", error: responseError(error))
alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors)
progressIndicator = false
}
}
} catch let error {
await MainActor.run {
alert = .error(title: "Error exporting chat database", error: responseError(error))
progressIndicator = false
}
}
return false
}
private func importArchive(_ archivePath: URL) {
static func importArchive(
_ archivePath: URL,
_ progressIndicator: Binding<Bool>,
_ alert: Binding<DatabaseAlert?>
) async -> Bool {
if archivePath.startAccessingSecurityScopedResource() {
progressIndicator = true
Task {
do {
try await apiDeleteStorage()
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
do {
let config = ArchiveConfig(archivePath: archivePath.path)
let archiveErrors = try await apiImportArchive(config: config)
_ = kcDatabasePassword.remove()
if archiveErrors.isEmpty {
await operationEnded(.archiveImported)
} else {
await operationEnded(.archiveImportedWithErrors(archiveErrors: archiveErrors))
}
} catch let error {
await operationEnded(.error(title: "Error importing chat database", error: responseError(error)))
}
} catch let error {
await operationEnded(.error(title: "Error deleting chat database", error: responseError(error)))
}
archivePath.stopAccessingSecurityScopedResource()
await MainActor.run {
progressIndicator.wrappedValue = true
}
do {
try await apiDeleteStorage()
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
do {
let config = ArchiveConfig(archivePath: archivePath.path)
let archiveErrors = try await apiImportArchive(config: config)
shouldImportAppSettingsDefault.set(true)
_ = kcDatabasePassword.remove()
if archiveErrors.isEmpty {
await operationEnded(.archiveImported, progressIndicator, alert)
} else {
await operationEnded(.archiveImportedWithErrors(archiveErrors: archiveErrors), progressIndicator, alert)
}
return true
} catch let error {
await operationEnded(.error(title: "Error importing chat database", error: responseError(error)), progressIndicator, alert)
}
} catch let error {
await operationEnded(.error(title: "Error deleting chat database", error: responseError(error)), progressIndicator, alert)
}
archivePath.stopAccessingSecurityScopedResource()
} else {
alert = .error(title: "Error accessing database file")
showAlert("Error accessing database file")
}
return false
}
private func deleteChat() {
progressIndicator = true
Task {
do {
try await deleteChatAsync()
await operationEnded(.chatDeleted)
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
} catch let error {
await operationEnded(.error(title: "Error deleting database", error: responseError(error)))
}
private func deleteChat() async -> Bool {
await MainActor.run {
progressIndicator = true
}
do {
try await deleteChatAsync()
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
await DatabaseView.operationEnded(.chatDeleted, $progressIndicator, $alert)
return true
} catch let error {
await DatabaseView.operationEnded(.error(title: "Error deleting database", error: responseError(error)), $progressIndicator, $alert)
return false
}
}
@@ -428,39 +523,28 @@ struct DatabaseView: View {
}
}
private func operationEnded(_ dbAlert: DatabaseAlert) async {
private static func operationEnded(_ dbAlert: DatabaseAlert, _ progressIndicator: Binding<Bool>, _ alert: Binding<DatabaseAlert?>) async {
await MainActor.run {
let m = ChatModel.shared
m.chatDbChanged = true
m.chatInitialized = false
progressIndicator = false
alert = dbAlert
progressIndicator.wrappedValue = false
}
}
private func startChat() {
if m.chatDbChanged {
dismissSettingsSheet()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
resetChatCtrl()
do {
try initializeChat(start: true)
m.chatDbChanged = false
AppChatState.shared.set(.active)
} catch let error {
fatalError("Error starting chat \(responseError(error))")
}
}
} else {
do {
_ = try apiStartChat()
runChat = true
m.chatRunning = true
ChatReceiver.shared.start()
chatLastStartGroupDefault.set(Date.now)
AppChatState.shared.set(.active)
} catch let error {
runChat = false
alert = .error(title: "Error starting chat", error: responseError(error))
await withCheckedContinuation { cont in
let okAlertActionWaiting = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default, handler: { _ in cont.resume() })
// show these alerts globally so they are visible when all sheets will be hidden
if case .archiveImported = dbAlert {
let (title, message) = archiveImportedAlertText()
showAlert(title, message: message, actions: { [okAlertActionWaiting] })
} else if case .archiveImportedWithErrors(let errs) = dbAlert {
let (title, message) = archiveImportedWithErrorsAlertText(errs: errs)
showAlert(title, message: message, actions: { [okAlertActionWaiting] })
} else if case .chatDeleted = dbAlert {
let (title, message) = chatDeletedAlertText()
showAlert(title, message: message, actions: { [okAlertActionWaiting] })
} else {
alert.wrappedValue = dbAlert
cont.resume()
}
}
}
@@ -503,8 +587,28 @@ struct DatabaseView: View {
}
}
func archiveErrorsText(_ errs: [ArchiveError]) -> Text {
return Text("\n" + errs.map(showArchiveError).joined(separator: "\n"))
private func archiveImportedAlertText() -> (String, String) {
(
NSLocalizedString("Chat database imported", comment: ""),
NSLocalizedString("Restart the app to use imported chat database", comment: "")
)
}
private func archiveImportedWithErrorsAlertText(errs: [ArchiveError]) -> (String, String) {
(
NSLocalizedString("Chat database imported", comment: ""),
NSLocalizedString("Restart the app to use imported chat database", comment: "") + "\n" + NSLocalizedString("Some non-fatal errors occurred during import:", comment: "") + archiveErrorsText(errs)
)
}
private func chatDeletedAlertText() -> (String, String) {
(
NSLocalizedString("Chat database deleted", comment: ""),
NSLocalizedString("Restart the app to create a new chat profile", comment: "")
)
}
func archiveErrorsText(_ errs: [ArchiveError]) -> String {
return "\n" + errs.map(showArchiveError).joined(separator: "\n")
func showArchiveError(_ err: ArchiveError) -> String {
switch err {
@@ -117,7 +117,7 @@ struct MigrateToAppGroupView: View {
setV3DBMigration(.migration_error)
migrationError = "Error starting chat: \(responseError(error))"
}
deleteOldArchive()
deleteOldChatArchive()
} label: {
Text("Start chat")
.font(.title)
@@ -235,14 +235,16 @@ func exportChatArchive(_ storagePath: URL? = nil) async throws -> (URL, [Archive
try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true)
let errs = try await apiExportArchive(config: config)
if storagePath == nil {
deleteOldArchive()
deleteOldChatArchive()
UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME)
chatArchiveTimeDefault.set(archiveTime)
}
return (archivePath, errs)
}
func deleteOldArchive() {
/// Deprecated. Remove in the end of 2025. All unused archives should be deleted for the most users til then.
/// Remove DEFAULT_CHAT_ARCHIVE_NAME and DEFAULT_CHAT_ARCHIVE_TIME as well
func deleteOldChatArchive() {
let d = UserDefaults.standard
if let archiveName = d.string(forKey: DEFAULT_CHAT_ARCHIVE_NAME) {
do {
@@ -177,7 +177,7 @@ struct MigrateFromDevice: View {
case let .archiveExportedWithErrors(archivePath, errs):
return Alert(
title: Text("Chat database exported"),
message: Text("You may migrate the exported database.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs),
message: Text("You may migrate the exported database.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + Text(archiveErrorsText(errs)),
dismissButton: .default(Text("Continue")) {
Task { await uploadArchive(path: archivePath) }
}
@@ -222,7 +222,8 @@ struct MigrateFromDevice: View {
}
private func passphraseNotSetView() -> some View {
DatabaseEncryptionView(useKeychain: $useKeychain, migration: true)
DatabaseEncryptionView(useKeychain: $useKeychain, migration: true, stopChatRunBlockStartChat: { _, _ in
})
.onChange(of: initialRandomDBPassphrase) { initial in
if !initial {
migrationState = .uploadConfirmation
@@ -103,6 +103,9 @@ struct MigrateToDevice: View {
@State private var showQRCodeScanner: Bool = true
@State private var pasteboardHasStrings = UIPasteboard.general.hasStrings
@State private var importingArchiveFromFileProgressIndicator = false
@State private var showFileImporter = false
var body: some View {
VStack {
switch migrationState {
@@ -200,6 +203,12 @@ struct MigrateToDevice: View {
Section(header: Text("Or paste archive link").foregroundColor(theme.colors.secondary)) {
pasteLinkView()
}
Section(header: Text("Or import archive file").foregroundColor(theme.colors.secondary)) {
archiveImportFromFileView()
}
}
if importingArchiveFromFileProgressIndicator {
progressView()
}
}
}
@@ -220,6 +229,34 @@ struct MigrateToDevice: View {
.frame(maxWidth: .infinity, alignment: .center)
}
private func archiveImportFromFileView() -> some View {
Button {
showFileImporter = true
} label: {
Label("Import database", systemImage: "square.and.arrow.down")
}
.disabled(importingArchiveFromFileProgressIndicator)
.fileImporter(
isPresented: $showFileImporter,
allowedContentTypes: [.zip],
allowsMultipleSelection: false
) { result in
if case let .success(files) = result, let fileURL = files.first {
Task {
let success = await DatabaseView.importArchive(fileURL, $importingArchiveFromFileProgressIndicator, Binding.constant(nil))
if success {
DatabaseView.startChat(
Binding.constant(false),
$importingArchiveFromFileProgressIndicator
)
hideView()
}
}
}
}
}
private func linkDownloadingView(_ link: String) -> some View {
ZStack {
List {
@@ -18,7 +18,6 @@ struct NewChatMenuButton: View {
// @EnvironmentObject var chatModel: ChatModel
@State private var showNewChatSheet = false
@State private var alert: SomeAlert? = nil
@State private var pendingConnection: PendingContactConnection? = nil
var body: some View {
Button {
@@ -30,12 +29,8 @@ struct NewChatMenuButton: View {
.frame(width: 24, height: 24)
}
.appSheet(isPresented: $showNewChatSheet) {
NewChatSheet(pendingConnection: $pendingConnection)
NewChatSheet()
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
.onDisappear {
alert = cleanupPendingConnection(contactConnection: pendingConnection)
pendingConnection = nil
}
}
.alert(item: $alert) { a in
return a.alert
@@ -55,7 +50,6 @@ struct NewChatSheet: View {
@State private var searchShowingSimplexLink = false
@State private var searchChatFilteredBySimplexLink: String? = nil
@State private var alert: SomeAlert?
@Binding var pendingConnection: PendingContactConnection?
// Sheet height management
@State private var isAddContactActive = false
@@ -110,17 +104,17 @@ struct NewChatSheet: View {
if (searchText.isEmpty) {
Section {
NavigationLink(isActive: $isAddContactActive) {
NewChatView(selection: .invite, parentAlert: $alert, contactConnection: $pendingConnection)
NewChatView(selection: .invite)
.navigationTitle("New chat")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
} label: {
navigateOnTap(Label("Add contact", systemImage: "link.badge.plus")) {
navigateOnTap(Label("Create 1-time link", systemImage: "link.badge.plus")) {
isAddContactActive = true
}
}
NavigationLink(isActive: $isScanPasteLinkActive) {
NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert, contactConnection: $pendingConnection)
NewChatView(selection: .connect, showQRCodeScanner: true)
.navigationTitle("New chat")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
+28 -30
View File
@@ -45,32 +45,33 @@ enum NewChatOption: Identifiable {
var id: Self { self }
}
func cleanupPendingConnection(contactConnection: PendingContactConnection?) -> SomeAlert? {
var alert: SomeAlert? = nil
if !(ChatModel.shared.showingInvitation?.connChatUsed ?? true),
let conn = contactConnection {
alert = SomeAlert(
alert: Alert(
title: Text("Keep unused invitation?"),
message: Text("You can view invitation link again in connection details."),
primaryButton: .default(Text("Keep")) {},
secondaryButton: .destructive(Text("Delete")) {
Task {
await deleteChat(Chat(
chatInfo: .contactConnection(contactConnection: conn),
chatItems: []
))
func showKeepInvitationAlert() {
if let showingInvitation = ChatModel.shared.showingInvitation,
!showingInvitation.connChatUsed {
showAlert(
NSLocalizedString("Keep unused invitation?", comment: "alert title"),
message: NSLocalizedString("You can view invitation link again in connection details.", comment: "alert message"),
actions: {[
UIAlertAction(
title: NSLocalizedString("Keep", comment: "alert action"),
style: .default
),
UIAlertAction(
title: NSLocalizedString("Delete", comment: "alert action"),
style: .destructive,
handler: { _ in
Task {
await deleteChat(Chat(
chatInfo: .contactConnection(contactConnection: showingInvitation.pcc),
chatItems: []
))
}
}
}
),
id: "keepUnusedInvitation"
)
]}
)
}
ChatModel.shared.showingInvitation = nil
return alert
}
struct NewChatView: View {
@@ -84,13 +85,12 @@ struct NewChatView: View {
@State var choosingProfile = false
@State private var pastedLink: String = ""
@State private var alert: NewChatViewAlert?
@Binding var parentAlert: SomeAlert?
@Binding var contactConnection: PendingContactConnection?
@State private var contactConnection: PendingContactConnection? = nil
var body: some View {
VStack(alignment: .leading) {
Picker("New chat", selection: $selection) {
Label("Add contact", systemImage: "link")
Label("1-time link", systemImage: "link")
.tag(NewChatOption.invite)
Label("Connect via link", systemImage: "qrcode")
.tag(NewChatOption.connect)
@@ -157,7 +157,7 @@ struct NewChatView: View {
}
.onDisappear {
if !choosingProfile {
parentAlert = cleanupPendingConnection(contactConnection: contactConnection)
showKeepInvitationAlert()
contactConnection = nil
}
}
@@ -197,7 +197,7 @@ struct NewChatView: View {
if let (connReq, pcc) = r {
await MainActor.run {
m.updateContactConnection(pcc)
m.showingInvitation = ShowingInvitation(connId: pcc.id, connChatUsed: false)
m.showingInvitation = ShowingInvitation(pcc: pcc, connChatUsed: false)
connReqInvitation = connReq
contactConnection = pcc
}
@@ -1278,9 +1278,7 @@ struct NewChatView_Previews: PreviewProvider {
@State var contactConnection: PendingContactConnection? = nil
NewChatView(
selection: .invite,
parentAlert: $parentAlert,
contactConnection: $contactConnection
selection: .invite
)
}
}
@@ -34,13 +34,7 @@ struct AddressCreationCard: View {
Text("Your SimpleX address")
.font(.title3)
Spacer()
HStack(alignment: .center) {
Text("How to use it")
VStack {
Image(systemName: "info.circle")
.foregroundColor(theme.colors.secondary)
}
}
Text("How to use it") + textSpace + Text(Image(systemName: "info.circle")).foregroundColor(theme.colors.secondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -68,7 +62,7 @@ struct AddressCreationCard: View {
.alert(isPresented: $showAddressCreationAlert) {
Alert(
title: Text("SimpleX address"),
message: Text("You can create it in user picker."),
message: Text("Tap Create SimpleX address in the menu to create it later."),
dismissButton: .default(Text("Ok")) {
withAnimation {
addressCreationCardShown = true
@@ -87,8 +81,8 @@ struct AddressCreationCard: View {
.sheet(isPresented: $showAddressInfoSheet) {
NavigationView {
UserAddressLearnMore(showCreateAddressButton: true)
.navigationTitle("SimpleX address")
.navigationBarTitleDisplayMode(.large)
.navigationTitle("Address or 1-time link?")
.navigationBarTitleDisplayMode(.inline)
.modifier(ThemedBackground(grouped: true))
}
}
@@ -73,14 +73,20 @@ struct ChooseServerOperators: View {
GeometryReader { g in
ScrollView {
VStack(alignment: .leading, spacing: 20) {
if !onboarding {
Text("Choose operators")
.font(.largeTitle)
.bold()
let title = Text("Server operators")
.font(.largeTitle)
.bold()
.frame(maxWidth: .infinity, alignment: .center)
if onboarding {
title.padding(.top, 50)
} else {
title
}
infoText()
.frame(maxWidth: .infinity, alignment: .center)
Spacer()
ForEach(serverOperators) { srvOperator in
@@ -117,24 +123,24 @@ struct ChooseServerOperators: View {
.foregroundColor(.clear)
}
}
.font(.callout)
.padding(.top)
.font(.system(size: 17, weight: .semibold))
.frame(minHeight: 40)
}
}
.padding(.bottom)
if !onboarding && !reviewForOperators.isEmpty {
VStack(spacing: 8) {
reviewLaterButton()
(
Text("Conditions will be accepted for enabled operators after 30 days.")
+ Text(" ")
+ textSpace
+ Text("You can configure operators in Network & servers settings.")
)
.multilineTextAlignment(.center)
.font(.footnote)
.padding(.horizontal, 32)
}
.frame(maxWidth: .infinity)
.disabled(!canReviewLater)
.padding(.bottom)
}
@@ -162,21 +168,15 @@ struct ChooseServerOperators: View {
}
}
.frame(maxHeight: .infinity)
.padding()
.padding(onboarding ? 25 : 16)
}
private func infoText() -> some View {
HStack(spacing: 12) {
Image(systemName: "info.circle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundColor(theme.colors.primary)
.onTapGesture {
sheetItem = .showInfo
}
Text("Select network operators to use.")
Button {
sheetItem = .showInfo
} label: {
Label("How it helps privacy", systemImage: "info.circle")
.font(.headline)
}
}
@@ -305,8 +305,6 @@ struct ChooseServerOperators: View {
private func notificationsModeDestinationView() -> some View {
SetNotificationsMode()
.navigationTitle("Push notifications")
.navigationBarTitleDisplayMode(.large)
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
}
@@ -330,12 +328,12 @@ struct ChooseServerOperators: View {
Text("Conditions will be accepted for operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.")
}
ConditionsTextView()
.frame(maxHeight: .infinity)
acceptConditionsButton()
.padding(.bottom)
.padding(.bottom)
}
.padding(.horizontal)
.frame(maxHeight: .infinity)
.padding(.horizontal, 25)
}
private func acceptConditionsButton() -> some View {
@@ -408,18 +406,21 @@ struct ChooseServerOperators: View {
}
}
let operatorsPostLink = URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html")!
struct ChooseServerOperatorsInfoView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Network operators")
Text("Server operators")
.font(.largeTitle)
.bold()
.padding(.vertical)
ScrollView {
VStack(alignment: .leading) {
Group {
Text("When more than one network operator is enabled, the app will use the servers of different operators for each conversation.")
Text("For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing.")
Text("The app protects your privacy by using different operators in each conversation.")
Text("When more than one operator is enabled, none of them has metadata to learn who communicates with whom.")
Text("For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server.")
}
.padding(.bottom)
}
@@ -119,49 +119,67 @@ struct CreateFirstProfile: View {
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Text("Your profile, contacts and delivered messages are stored on your device.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
Text("The profile is only shared with your contacts.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
VStack(alignment: .center, spacing: 20) {
Text("Create your profile")
.font(.largeTitle)
.bold()
.multilineTextAlignment(.center)
Text("Your profile, contacts and delivered messages are stored on your device.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
.multilineTextAlignment(.center)
Text("The profile is only shared with your contacts.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity) // Ensures it takes up the full width
.padding(.top, 25)
.padding(.horizontal, 10)
HStack {
let name = displayName.trimmingCharacters(in: .whitespaces)
let validName = mkValidName(name)
ZStack {
ZStack(alignment: .trailing) {
TextField("Enter your name…", text: $displayName)
.focused($focusDisplayName)
.padding(.horizontal)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color(uiColor: .tertiarySystemFill))
)
if name != validName {
Button {
showAlert(.invalidNameError(validName: validName))
} label: {
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
Image(systemName: "exclamationmark.circle")
.foregroundColor(.red)
.padding(.horizontal, 10)
}
} else {
Image(systemName: "exclamationmark.circle").foregroundColor(.clear)
Image(systemName: "pencil").foregroundColor(theme.colors.secondary)
}
}
TextField("Enter your name…", text: $displayName)
.focused($focusDisplayName)
.padding(.horizontal)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color(uiColor: .tertiarySystemFill))
)
}
.padding(.top)
Spacer()
createProfileButton()
.padding(.bottom)
VStack(spacing: 10) {
createProfileButton()
if !focusDisplayName {
onboardingButtonPlaceholder()
}
}
}
.onAppear() {
focusDisplayName = true
setLastVersionDefault()
}
.padding()
.padding(.horizontal, 25)
.padding(.top, 10)
.padding(.bottom, 25)
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -191,8 +209,6 @@ struct CreateFirstProfile: View {
private func nextStepDestinationView() -> some View {
ChooseServerOperators(onboarding: true)
.navigationTitle("Choose operators")
.navigationBarTitleDisplayMode(.large)
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
}
@@ -23,13 +23,10 @@ struct HowItWorks: View {
ScrollView {
VStack(alignment: .leading) {
Group {
Text("Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*")
Text("To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.")
Text("You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.")
Text("Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.")
if onboarding {
Text("Read more in our GitHub repository.")
} else {
Text("To protect your privacy, SimpleX uses separate IDs for each of your contacts.")
Text("Only client devices store user profiles, contacts, groups, and messages.")
Text("All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.")
if !onboarding {
Text("Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme).")
}
}
@@ -40,12 +37,14 @@ struct HowItWorks: View {
Spacer()
if onboarding {
createFirstProfileButton()
.padding(.bottom)
VStack(spacing: 10) {
createFirstProfileButton()
onboardingButtonPlaceholder()
}
}
}
.lineLimit(10)
.padding()
.padding(onboarding ? 25 : 16)
.frame(maxHeight: .infinity, alignment: .top)
.modifier(ThemedBackground())
}
@@ -24,14 +24,10 @@ struct OnboardingView: View {
CreateSimpleXAddress()
case .step3_ChooseServerOperators:
ChooseServerOperators(onboarding: true)
.navigationTitle("Choose operators")
.navigationBarTitleDisplayMode(.large)
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
case .step4_SetNotificationsMode:
SetNotificationsMode()
.navigationTitle("Push notifications")
.navigationBarTitleDisplayMode(.large)
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
case .onboardingComplete: EmptyView()
@@ -40,6 +36,10 @@ struct OnboardingView: View {
}
}
func onboardingButtonPlaceholder() -> some View {
Spacer().frame(height: 40)
}
enum OnboardingStage: String, Identifiable {
case step1_SimpleXInfo
case step2_CreateProfile // deprecated
@@ -13,41 +13,55 @@ struct SetNotificationsMode: View {
@EnvironmentObject var m: ChatModel
@State private var notificationMode = NotificationsMode.instant
@State private var showAlert: NotificationAlert?
@State private var showInfo: Bool = false
var body: some View {
GeometryReader { g in
ScrollView {
VStack(alignment: .leading, spacing: 20) {
Text("Send notifications:")
VStack(alignment: .center, spacing: 20) {
Text("Push Notifications")
.font(.largeTitle)
.bold()
.padding(.top, 50)
infoText()
Spacer()
ForEach(NotificationsMode.values) { mode in
NtfModeSelector(mode: mode, selection: $notificationMode)
}
Spacer()
Button {
if let token = m.deviceToken {
setNotificationsMode(token, notificationMode)
} else {
AlertManager.shared.showAlertMsg(title: "No device token!")
}
onboardingStageDefault.set(.onboardingComplete)
m.onboardingStage = .onboardingComplete
} label: {
if case .off = notificationMode {
Text("Use chat")
} else {
Text("Enable notifications")
VStack(spacing: 10) {
Button {
if let token = m.deviceToken {
setNotificationsMode(token, notificationMode)
} else {
AlertManager.shared.showAlertMsg(title: "No device token!")
}
onboardingStageDefault.set(.onboardingComplete)
m.onboardingStage = .onboardingComplete
} label: {
if case .off = notificationMode {
Text("Use chat")
} else {
Text("Enable notifications")
}
}
.buttonStyle(OnboardingButtonStyle())
onboardingButtonPlaceholder()
}
.buttonStyle(OnboardingButtonStyle())
.padding(.bottom)
}
.padding()
.padding(25)
.frame(minHeight: g.size.height)
}
}
.frame(maxHeight: .infinity)
.sheet(isPresented: $showInfo) {
NotificationsInfoView()
}
}
private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) {
@@ -73,6 +87,15 @@ struct SetNotificationsMode: View {
}
}
}
private func infoText() -> some View {
Button {
showInfo = true
} label: {
Label("How it affects privacy", systemImage: "info.circle")
.font(.headline)
}
}
}
struct NtfModeSelector: View {
@@ -83,15 +106,24 @@ struct NtfModeSelector: View {
var body: some View {
ZStack {
VStack(alignment: .leading, spacing: 4) {
Text(mode.label)
.font(.headline)
HStack(spacing: 16) {
Image(systemName: mode.icon)
.resizable()
.scaledToFill()
.frame(width: mode.icon == "bolt" ? 14 : 18, height: 18)
.foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary)
Text(ntfModeDescription(mode))
.lineLimit(10)
.font(.subheadline)
VStack(alignment: .leading, spacing: 4) {
Text(mode.label)
.font(.headline)
.foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary)
Text(ntfModeShortDescription(mode))
.lineLimit(2)
.font(.callout)
}
}
.padding(12)
.padding(.vertical, 12)
.padding(.trailing, 12)
.padding(.leading, 16)
}
.frame(maxWidth: .infinity, alignment: .leading)
.background(tapped ? Color(uiColor: .secondarySystemFill) : theme.colors.background)
@@ -107,6 +139,37 @@ struct NtfModeSelector: View {
}
}
struct NotificationsInfoView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Notifications privacy")
.font(.largeTitle)
.bold()
.padding(.vertical)
ScrollView {
VStack(alignment: .leading) {
Group {
ForEach(NotificationsMode.values) { mode in
VStack(alignment: .leading, spacing: 4) {
(Text(Image(systemName: mode.icon)) + textSpace + Text(mode.label))
.font(.headline)
.foregroundColor(.secondary)
Text(ntfModeDescription(mode))
.lineLimit(10)
.font(.callout)
}
}
}
.padding(.bottom)
}
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.modifier(ThemedBackground())
}
}
struct NotificationsModeView_Previews: PreviewProvider {
static var previews: some View {
SetNotificationsMode()
@@ -19,51 +19,52 @@ struct SimpleXInfo: View {
var body: some View {
GeometryReader { g in
ScrollView {
VStack(alignment: .leading, spacing: 20) {
Image(colorScheme == .light ? "logo" : "logo-light")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: g.size.width * 0.67)
.padding(.bottom, 8)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .top)
VStack(alignment: .leading) {
VStack(alignment: .center, spacing: 10) {
Image(colorScheme == .light ? "logo" : "logo-light")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: g.size.width * 0.67)
.padding(.bottom, 8)
.padding(.leading, 4)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .top)
Button {
showHowItWorks = true
} label: {
Label("The future of messaging", systemImage: "info.circle")
.font(.headline)
}
}
Spacer()
VStack(alignment: .leading) {
Text("The next generation of private messaging")
.font(.title2)
.padding(.bottom, 30)
.padding(.horizontal, 40)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
infoRow("privacy", "Privacy redefined",
"The 1st platform without any user identifiers private by design.", width: 48)
infoRow("shield", "Immune to spam and abuse",
"People can connect to you only via the links you share.", width: 46)
infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized",
"Open-source protocol and code anybody can run the servers.", width: 44)
onboardingInfoRow("privacy", "Privacy redefined",
"No user identifiers.", width: 48)
onboardingInfoRow("shield", "Immune to spam",
"You decide who can connect.", width: 46)
onboardingInfoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized",
"Anybody can host servers.", width: 46)
}
.padding(.leading, 16)
Spacer()
if onboarding {
createFirstProfileButton()
VStack(spacing: 10) {
createFirstProfileButton()
Button {
m.migrationState = .pasteOrScanLink
} label: {
Label("Migrate from another device", systemImage: "tray.and.arrow.down")
.font(.subheadline)
Button {
m.migrationState = .pasteOrScanLink
} label: {
Label("Migrate from another device", systemImage: "tray.and.arrow.down")
.font(.system(size: 17, weight: .semibold))
.frame(minHeight: 40)
}
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
}
Button {
showHowItWorks = true
} label: {
Label("How it works", systemImage: "info.circle")
.font(.subheadline)
}
.frame(maxWidth: .infinity)
.padding(.bottom)
}
.frame(minHeight: g.size.height)
}
@@ -89,26 +90,29 @@ struct SimpleXInfo: View {
}
}
.frame(maxHeight: .infinity)
.padding()
.padding(.horizontal, 25)
.padding(.top, 75)
.padding(.bottom, 25)
}
private func infoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View {
private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View {
HStack(alignment: .top) {
Image(image)
.resizable()
.scaledToFit()
.frame(width: width, height: 54)
.frame(width: 54)
.padding(.top, 4)
.padding(.leading, 4)
.padding(.trailing, 10)
VStack(alignment: .leading, spacing: 4) {
Text(title).font(.headline)
Text(text).frame(minHeight: 40, alignment: .top)
.font(.callout)
.lineLimit(3)
.fixedSize(horizontal: false, vertical: true)
}
.padding(.top, 4)
}
.padding(.bottom, 20)
.padding(.trailing, 6)
.padding(.bottom, 12)
}
private func createFirstProfileButton() -> some View {
@@ -121,7 +125,7 @@ struct SimpleXInfo: View {
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
NavigationLink(isActive: $createProfileNavLinkActive) {
createProfileDestinationView()
CreateFirstProfile()
} label: {
EmptyView()
}
@@ -129,15 +133,10 @@ struct SimpleXInfo: View {
.hidden()
}
}
private func createProfileDestinationView() -> some View {
CreateFirstProfile()
.navigationTitle("Create your profile")
.navigationBarTitleDisplayMode(.large)
.modifier(ThemedBackground())
}
}
let textSpace = Text(verbatim: " ")
struct SimpleXInfo_Previews: PreviewProvider {
static var previews: some View {
SimpleXInfo(onboarding: true)
@@ -521,7 +521,7 @@ private let versionDescriptions: [VersionDescription] = [
),
VersionDescription(
version: "v6.2 (beta.1)",
post: URL(string: "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"),
post: URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html"),
features: [
.view(FeatureView(
icon: nil,
@@ -529,9 +529,9 @@ private let versionDescriptions: [VersionDescription] = [
view: { NewOperatorsView() }
)),
.feature(Description(
icon: "text.quote",
title: "Improved chat navigation",
description: "- Open chat on the first unread message.\n- Jump to quoted messages."
icon: "bolt",
title: "More reliable notifications",
description: "Delivered even when Apple drops them."
)),
]
)
@@ -268,7 +268,7 @@ struct ConnectDesktopView: View {
private func ctrlDeviceNameText(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> Text {
var t = Text(rc?.deviceViewName ?? session.ctrlAppInfo?.deviceName ?? "")
if (rc == nil) {
t = t + Text(" ") + Text("(new)").italic()
t = t + textSpace + Text("(new)").italic()
}
return t
}
@@ -277,7 +277,7 @@ struct ConnectDesktopView: View {
let v = session.ctrlAppInfo?.appVersionRange.maxVersion
var t = Text("v\(v ?? "")")
if v != session.appVersion {
t = t + Text(" ") + Text("(this device v\(session.appVersion))").italic()
t = t + textSpace + Text("(this device v\(session.appVersion))").italic()
}
return t
}
@@ -45,7 +45,7 @@ struct DeveloperView: View {
} header: {
Text("")
} footer: {
((developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option."))
((developerTools ? Text("Show:") : Text("Hide:")) + textSpace + Text("Database IDs and Transport isolation option."))
.foregroundColor(theme.colors.secondary)
}
@@ -0,0 +1,83 @@
//
// ConditionsWebView.swift
// SimpleX (iOS)
//
// Created by Stanislav Dmitrenko on 26.11.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
import WebKit
struct ConditionsWebView: UIViewRepresentable {
@State var html: String
@EnvironmentObject var theme: AppTheme
@State var pageLoaded = false
func makeUIView(context: Context) -> WKWebView {
let view = WKWebView()
view.backgroundColor = .clear
view.isOpaque = false
view.navigationDelegate = context.coordinator
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// just to make sure that even if updateUIView will not be called for any reason, the page
// will be rendered anyway
if !pageLoaded {
loadPage(view)
}
}
return view
}
func updateUIView(_ view: WKWebView, context: Context) {
loadPage(view)
}
private func loadPage(_ webView: WKWebView) {
let styles = """
<style>
body {
color: \(theme.colors.onBackground.toHTMLHex());
font-family: Helvetica;
}
a {
color: \(theme.colors.primary.toHTMLHex());
}
code, pre {
font-family: Menlo;
background: \(theme.colors.secondary.opacity(theme.colors.isLight ? 0.2 : 0.3).toHTMLHex());
}
</style>
"""
let head = "<head><meta name='viewport' content='width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=no'>\(styles)</head>"
webView.loadHTMLString(head + html, baseURL: nil)
DispatchQueue.main.async {
pageLoaded = true
}
}
func makeCoordinator() -> Cordinator {
Cordinator()
}
class Cordinator: NSObject, WKNavigationDelegate {
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else { return decisionHandler(.allow) }
switch navigationAction.navigationType {
case .linkActivated:
decisionHandler(.cancel)
if url.absoluteString.starts(with: "https://simplex.chat/contact#") {
ChatModel.shared.appOpenUrl = url
} else {
UIApplication.shared.open(url)
}
default:
decisionHandler(.allow)
}
}
}
}
@@ -269,6 +269,7 @@ struct UsageConditionsView: View {
}
.padding(.bottom)
.padding(.bottom)
case let .accepted(operators):
Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.")
@@ -277,7 +278,7 @@ struct UsageConditionsView: View {
.padding(.bottom)
}
}
.padding(.horizontal)
.padding(.horizontal, 25)
.frame(maxHeight: .infinity)
}
@@ -8,6 +8,7 @@
import SwiftUI
import SimpleXChat
import Ink
struct OperatorView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@@ -342,6 +343,7 @@ struct OperatorInfoView: View {
struct ConditionsTextView: View {
@State private var conditionsData: (UsageConditions, String?, UsageConditions?)?
@State private var failedToLoad: Bool = false
@State private var conditionsHTML: String? = nil
let defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
@@ -350,7 +352,18 @@ struct ConditionsTextView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.task {
do {
conditionsData = try await getUsageConditions()
let conditions = try await getUsageConditions()
let conditionsText = conditions.1
let parentLink = "https://github.com/simplex-chat/simplex-chat/blob/\(conditions.0.conditionsCommit)"
let preparedText: String?
if let conditionsText {
let prepared = prepareMarkdown(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines), parentLink)
conditionsHTML = MarkdownParser().html(from: prepared)
preparedText = prepared
} else {
preparedText = nil
}
conditionsData = (conditions.0, preparedText, conditions.2)
} catch let error {
logger.error("ConditionsTextView getUsageConditions error: \(responseError(error))")
failedToLoad = true
@@ -358,18 +371,16 @@ struct ConditionsTextView: View {
}
}
// TODO Markdown & diff rendering
// TODO Diff rendering
@ViewBuilder private func viewBody() -> some View {
if let (usageConditions, conditionsText, acceptedConditions) = conditionsData {
if let conditionsText = conditionsText {
ScrollView {
Text(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines))
.padding()
}
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .secondarySystemGroupedBackground))
)
if let (usageConditions, _, _) = conditionsData {
if let conditionsHTML {
ConditionsWebView(html: conditionsHTML)
.padding(6)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .secondarySystemGroupedBackground))
)
} else {
let conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/\(usageConditions.conditionsCommit)/PRIVACY.md"
conditionsLinkView(conditionsLink)
@@ -391,6 +402,16 @@ struct ConditionsTextView: View {
}
}
}
private func prepareMarkdown(_ text: String, _ parentLink: String) -> String {
let localLinkRegex = try! NSRegularExpression(pattern: "\\[([^\\(]*)\\]\\(#.*\\)")
let h1Regex = try! NSRegularExpression(pattern: "^# ")
var text = localLinkRegex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "$1")
text = h1Regex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "")
return text
.replacingOccurrences(of: "](/", with: "](\(parentLink)/")
.replacingOccurrences(of: "](./", with: "](\(parentLink)/")
}
}
struct SingleOperatorUsageConditionsView: View {
@@ -237,9 +237,17 @@ struct NotificationsView: View {
func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey {
switch mode {
case .off: return "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)."
case .periodic: return "**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have."
case .instant: return "**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from."
case .off: return "**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app."
case .periodic: return "**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata."
case .instant: return "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from."
}
}
func ntfModeShortDescription(_ mode: NotificationsMode) -> LocalizedStringKey {
switch mode {
case .off: return "Check messages when allowed."
case .periodic: return "Check messages every 20 min."
case .instant: return "E2E encrypted notifications."
}
}
@@ -39,6 +39,7 @@ let DEFAULT_EXPERIMENTAL_CALLS = "experimentalCalls"
let DEFAULT_CHAT_ARCHIVE_NAME = "chatArchiveName"
let DEFAULT_CHAT_ARCHIVE_TIME = "chatArchiveTime"
let DEFAULT_CHAT_V3_DB_MIGRATION = "chatV3DBMigration"
let DEFAULT_SHOULD_IMPORT_APP_SETTINGS = "shouldImportAppSettings"
let DEFAULT_DEVELOPER_TOOLS = "developerTools"
let DEFAULT_ENCRYPTION_STARTED = "encryptionStarted"
let DEFAULT_ENCRYPTION_STARTED_AT = "encryptionStartedAt"
@@ -192,6 +193,8 @@ let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.sta
let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE)
let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE)
/// after importing new database, this flag will be set and unset only after importing app settings in `initializeChat` */
let shouldImportAppSettingsDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOULD_IMPORT_APP_SETTINGS)
let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME)
let systemDarkThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SYSTEM_DARK_THEME, withDefault: DefaultTheme.DARK.themeName)
let currentThemeIdsDefault = CodableDefault<[String: String]>(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME_IDS, withDefault: [:] )
@@ -11,25 +11,50 @@ import SwiftUI
struct UserAddressLearnMore: View {
@State var showCreateAddressButton = false
@State private var createAddressLinkActive = false
@State private var createOneTimeLinkActive = false
var body: some View {
VStack {
List {
VStack(alignment: .leading, spacing: 18) {
Text("You can share your address as a link or QR code - anybody can connect to you.")
VStack(alignment: .leading, spacing: 12) {
(Text(Image(systemName: "envelope")).foregroundColor(.secondary) + textSpace + Text("Share address publicly").bold().font(.title2))
Text("Share SimpleX address on social media.")
Text("You won't lose your contacts if you later delete your address.")
Text("When people request to connect, you can accept or reject it.")
Text("Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).")
(Text(Image(systemName: "link.badge.plus")).foregroundColor(.secondary) + textSpace + Text("Share 1-time link with a friend").font(.title2).bold())
.padding(.top)
Text("1-time link can be used *with one contact only* - share in person or via any messenger.")
Text("You can set connection name, to remember who the link was shared with.")
if !showCreateAddressButton {
(Text(Image(systemName: "shield")).foregroundColor(.secondary) + textSpace + Text("Connection security").font(.title2).bold())
.padding(.top)
Text("SimpleX address and 1-time links are safe to share via any messenger.")
Text("To protect against your link being replaced, you can compare contact security codes.")
Text("Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).")
.padding(.top)
}
}
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
.frame(maxHeight: .infinity)
.frame(maxHeight: .infinity, alignment: .top)
Spacer()
if showCreateAddressButton {
addressCreationButton()
.padding()
VStack {
addressCreationButton()
.padding(.bottom)
createOneTimeLinkButton()
}
.padding()
}
}
.frame(maxHeight: .infinity, alignment: .top)
}
private func addressCreationButton() -> some View {
@@ -52,6 +77,28 @@ struct UserAddressLearnMore: View {
.hidden()
}
}
private func createOneTimeLinkButton() -> some View {
ZStack {
Button {
createOneTimeLinkActive = true
} label: {
Text("Create 1-time link")
.font(.callout)
}
NavigationLink(isActive: $createOneTimeLinkActive) {
NewChatView(selection: .invite)
.navigationTitle("New chat")
.navigationBarTitleDisplayMode(.large)
.modifier(ThemedBackground(grouped: true))
} label: {
EmptyView()
}
.frame(width: 1, height: 1)
.hidden()
}
}
}
struct UserAddressLearnMore_Previews: PreviewProvider {
@@ -16,45 +16,28 @@ struct UserAddressView: View {
@EnvironmentObject var theme: AppTheme
@State var shareViaProfile = false
@State var autoCreate = false
@State private var aas = AutoAcceptState()
@State private var savedAAS = AutoAcceptState()
@State private var ignoreShareViaProfileChange = false
@State private var showMailView = false
@State private var mailViewResult: Result<MFMailComposeResult, Error>? = nil
@State private var alert: UserAddressAlert?
@State private var progressIndicator = false
@FocusState private var keyboardVisible: Bool
private enum UserAddressAlert: Identifiable {
case deleteAddress
case profileAddress(on: Bool)
case shareOnCreate
case error(title: LocalizedStringKey, error: LocalizedStringKey?)
var id: String {
switch self {
case .deleteAddress: return "deleteAddress"
case let .profileAddress(on): return "profileAddress \(on)"
case .shareOnCreate: return "shareOnCreate"
case let .error(title, _): return "error \(title)"
}
}
}
var body: some View {
ZStack {
userAddressScrollView()
.onDisappear {
if savedAAS != aas {
showAlert(
title: NSLocalizedString("Auto-accept settings", comment: "alert title"),
message: NSLocalizedString("Settings were changed.", comment: "alert message"),
buttonTitle: NSLocalizedString("Save", comment: "alert button"),
buttonAction: saveAAS,
cancelButton: true
)
}
}
userAddressView()
if progressIndicator {
ZStack {
@@ -75,39 +58,22 @@ struct UserAddressView: View {
}
}
@Namespace private var bottomID
private func userAddressScrollView() -> some View {
ScrollViewReader { proxy in
userAddressView()
.onChange(of: keyboardVisible) { _ in
if keyboardVisible {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation {
proxy.scrollTo(bottomID, anchor: .top)
}
}
}
}
}
}
private func userAddressView() -> some View {
List {
if let userAddress = chatModel.userAddress {
existingAddressView(userAddress)
.onAppear {
aas = AutoAcceptState(userAddress: userAddress)
savedAAS = aas
}
.onChange(of: aas.enable) { _ in
if !aas.enable { aas = AutoAcceptState() }
}
} else {
Section {
createAddressButton()
} footer: {
Text("Create an address to let people connect with you.")
} header: {
Text("For social media")
.foregroundColor(theme.colors.secondary)
}
Section {
createOneTimeLinkButton()
} header: {
Text("Or to share privately")
.foregroundColor(theme.colors.secondary)
}
@@ -123,8 +89,8 @@ struct UserAddressView: View {
title: Text("Delete address?"),
message:
shareViaProfile
? Text("All your contacts will remain connected. Profile update will be sent to your contacts.")
: Text("All your contacts will remain connected."),
? Text("All your contacts will remain connected. Profile update will be sent to your contacts.")
: Text("All your contacts will remain connected."),
primaryButton: .destructive(Text("Delete")) {
progressIndicator = true
Task {
@@ -134,7 +100,6 @@ struct UserAddressView: View {
chatModel.userAddress = nil
chatModel.updateUser(u)
if shareViaProfile {
ignoreShareViaProfileChange = true
shareViaProfile = false
}
}
@@ -147,37 +112,12 @@ struct UserAddressView: View {
}
}, secondaryButton: .cancel()
)
case let .profileAddress(on):
if on {
return Alert(
title: Text("Share address with contacts?"),
message: Text("Profile update will be sent to your contacts."),
primaryButton: .default(Text("Share")) {
setProfileAddress(on)
}, secondaryButton: .cancel() {
ignoreShareViaProfileChange = true
shareViaProfile = !on
}
)
} else {
return Alert(
title: Text("Stop sharing address?"),
message: Text("Profile update will be sent to your contacts."),
primaryButton: .default(Text("Stop sharing")) {
setProfileAddress(on)
}, secondaryButton: .cancel() {
ignoreShareViaProfileChange = true
shareViaProfile = !on
}
)
}
case .shareOnCreate:
return Alert(
title: Text("Share address with contacts?"),
message: Text("Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts."),
primaryButton: .default(Text("Share")) {
setProfileAddress(true)
ignoreShareViaProfileChange = true
setProfileAddress($progressIndicator, true)
shareViaProfile = true
}, secondaryButton: .cancel()
)
@@ -192,19 +132,24 @@ struct UserAddressView: View {
SimpleXLinkQRCode(uri: userAddress.connReqContact)
.id("simplex-contact-address-qrcode-\(userAddress.connReqContact)")
shareQRCodeButton(userAddress)
if MFMailComposeViewController.canSendMail() {
shareViaEmailButton(userAddress)
}
shareWithContactsButton()
autoAcceptToggle()
learnMoreButton()
// if MFMailComposeViewController.canSendMail() {
// shareViaEmailButton(userAddress)
// }
addressSettingsButton(userAddress)
} header: {
Text("Address")
Text("For social media")
.foregroundColor(theme.colors.secondary)
}
if aas.enable {
autoAcceptSection()
Section {
createOneTimeLinkButton()
} header: {
Text("Or to share privately")
.foregroundColor(theme.colors.secondary)
}
Section {
learnMoreButton()
}
Section {
@@ -213,7 +158,6 @@ struct UserAddressView: View {
Text("Your contacts will remain connected.")
.foregroundColor(theme.colors.secondary)
}
.id(bottomID)
}
private func createAddressButton() -> some View {
@@ -223,7 +167,7 @@ struct UserAddressView: View {
Label("Create SimpleX address", systemImage: "qrcode")
}
}
private func createAddress() {
progressIndicator = true
Task {
@@ -243,6 +187,18 @@ struct UserAddressView: View {
}
}
private func createOneTimeLinkButton() -> some View {
NavigationLink {
NewChatView(selection: .invite)
.navigationTitle("New chat")
.navigationBarTitleDisplayMode(.large)
.modifier(ThemedBackground(grouped: true))
} label: {
Label("Create 1-time link", systemImage: "link.badge.plus")
.foregroundColor(theme.colors.primary)
}
}
private func deleteAddressButton() -> some View {
Button(role: .destructive) {
alert = .deleteAddress
@@ -292,24 +248,136 @@ struct UserAddressView: View {
}
}
private func autoAcceptToggle() -> some View {
settingsRow("checkmark", color: theme.colors.secondary) {
Toggle("Auto-accept", isOn: $aas.enable)
.onChange(of: aas.enable) { _ in
saveAAS()
}
private func addressSettingsButton(_ userAddress: UserContactLink) -> some View {
NavigationLink {
UserAddressSettingsView(shareViaProfile: $shareViaProfile)
.navigationTitle("Address settings")
.navigationBarTitleDisplayMode(.large)
.modifier(ThemedBackground(grouped: true))
} label: {
Text("Address settings")
}
}
private func learnMoreButton() -> some View {
NavigationLink {
UserAddressLearnMore()
.navigationTitle("SimpleX address")
.navigationTitle("Address or 1-time link?")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(.large)
.navigationBarTitleDisplayMode(.inline)
} label: {
settingsRow("info.circle", color: theme.colors.secondary) {
Text("About SimpleX address")
Text("SimpleX address or 1-time link?")
}
}
}
}
private struct AutoAcceptState: Equatable {
var enable = false
var incognito = false
var welcomeText = ""
init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") {
self.enable = enable
self.incognito = incognito
self.welcomeText = welcomeText
}
init(userAddress: UserContactLink) {
if let aa = userAddress.autoAccept {
enable = true
incognito = aa.acceptIncognito
if let msg = aa.autoReply {
welcomeText = msg.text
} else {
welcomeText = ""
}
} else {
enable = false
incognito = false
welcomeText = ""
}
}
var autoAccept: AutoAccept? {
if enable {
var autoReply: MsgContent? = nil
let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines)
if s != "" { autoReply = .text(s) }
return AutoAccept(acceptIncognito: incognito, autoReply: autoReply)
}
return nil
}
}
private func setProfileAddress(_ progressIndicator: Binding<Bool>, _ on: Bool) {
progressIndicator.wrappedValue = true
Task {
do {
if let u = try await apiSetProfileAddress(on: on) {
DispatchQueue.main.async {
ChatModel.shared.updateUser(u)
}
}
await MainActor.run { progressIndicator.wrappedValue = false }
} catch let error {
logger.error("apiSetProfileAddress: \(responseError(error))")
await MainActor.run { progressIndicator.wrappedValue = false }
}
}
}
struct UserAddressSettingsView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@EnvironmentObject var theme: AppTheme
@Binding var shareViaProfile: Bool
@State private var aas = AutoAcceptState()
@State private var savedAAS = AutoAcceptState()
@State private var ignoreShareViaProfileChange = false
@State private var progressIndicator = false
@FocusState private var keyboardVisible: Bool
var body: some View {
ZStack {
if let userAddress = ChatModel.shared.userAddress {
userAddressSettingsView()
.onAppear {
aas = AutoAcceptState(userAddress: userAddress)
savedAAS = aas
}
.onChange(of: aas.enable) { aasEnabled in
if !aasEnabled { aas = AutoAcceptState() }
}
.onDisappear {
if savedAAS != aas {
showAlert(
title: NSLocalizedString("Auto-accept settings", comment: "alert title"),
message: NSLocalizedString("Settings were changed.", comment: "alert message"),
buttonTitle: NSLocalizedString("Save", comment: "alert button"),
buttonAction: saveAAS,
cancelButton: true
)
}
}
} else {
Text(String("Error opening address settings"))
}
if progressIndicator {
ProgressView().scaleEffect(2)
}
}
}
private func userAddressSettingsView() -> some View {
List {
Section {
shareWithContactsButton()
autoAcceptToggle()
}
if aas.enable {
autoAcceptSection()
}
}
}
@@ -321,68 +389,66 @@ struct UserAddressView: View {
if ignoreShareViaProfileChange {
ignoreShareViaProfileChange = false
} else {
alert = .profileAddress(on: on)
if on {
showAlert(
NSLocalizedString("Share address with contacts?", comment: "alert title"),
message: NSLocalizedString("Profile update will be sent to your contacts.", comment: "alert message"),
actions: {[
UIAlertAction(
title: NSLocalizedString("Cancel", comment: "alert action"),
style: .default,
handler: { _ in
ignoreShareViaProfileChange = true
shareViaProfile = !on
}
),
UIAlertAction(
title: NSLocalizedString("Share", comment: "alert action"),
style: .default,
handler: { _ in
setProfileAddress($progressIndicator, on)
}
)
]}
)
} else {
showAlert(
NSLocalizedString("Stop sharing address?", comment: "alert title"),
message: NSLocalizedString("Profile update will be sent to your contacts.", comment: "alert message"),
actions: {[
UIAlertAction(
title: NSLocalizedString("Cancel", comment: "alert action"),
style: .default,
handler: { _ in
ignoreShareViaProfileChange = true
shareViaProfile = !on
}
),
UIAlertAction(
title: NSLocalizedString("Stop sharing", comment: "alert action"),
style: .default,
handler: { _ in
setProfileAddress($progressIndicator, on)
}
)
]}
)
}
}
}
}
}
private func setProfileAddress(_ on: Bool) {
progressIndicator = true
Task {
do {
if let u = try await apiSetProfileAddress(on: on) {
DispatchQueue.main.async {
chatModel.updateUser(u)
}
private func autoAcceptToggle() -> some View {
settingsRow("checkmark", color: theme.colors.secondary) {
Toggle("Auto-accept", isOn: $aas.enable)
.onChange(of: aas.enable) { _ in
saveAAS()
}
await MainActor.run { progressIndicator = false }
} catch let error {
logger.error("UserAddressView apiSetProfileAddress: \(responseError(error))")
await MainActor.run { progressIndicator = false }
}
}
}
private struct AutoAcceptState: Equatable {
var enable = false
var incognito = false
var welcomeText = ""
init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") {
self.enable = enable
self.incognito = incognito
self.welcomeText = welcomeText
}
init(userAddress: UserContactLink) {
if let aa = userAddress.autoAccept {
enable = true
incognito = aa.acceptIncognito
if let msg = aa.autoReply {
welcomeText = msg.text
} else {
welcomeText = ""
}
} else {
enable = false
incognito = false
welcomeText = ""
}
}
var autoAccept: AutoAccept? {
if enable {
var autoReply: MsgContent? = nil
let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines)
if s != "" { autoReply = .text(s) }
return AutoAccept(acceptIncognito: incognito, autoReply: autoReply)
}
return nil
}
}
@ViewBuilder private func autoAcceptSection() -> some View {
private func autoAcceptSection() -> some View {
Section {
acceptIncognitoToggle()
welcomeMessageEditor()
@@ -434,7 +500,7 @@ struct UserAddressView: View {
Task {
do {
if let address = try await userAddressAutoAccept(aas.autoAccept) {
chatModel.userAddress = address
ChatModel.shared.userAddress = address
savedAAS = aas
}
} catch let error {
@@ -187,23 +187,18 @@
<target state="needs-translation">)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="translated">** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بكم.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve" approved="no">
<source>**Create link / QR code** for your contact to use.</source>
<target state="translated">** أنشئ رابطًا / رمز QR ** لتستخدمه جهة الاتصال الخاصة بك.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<target state="translated">** المزيد من الخصوصية **: تحققوا من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديكم.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<target state="translated">** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامكم للتطبيق).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -217,8 +212,8 @@
<target state="translated">** يرجى ملاحظة **: لن تتمكنوا من استعادة أو تغيير عبارة المرور إذا فقدتموها.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<target state="translated">** موصى به **: يتم إرسال رمز الجهاز والإشعارات إلى خادم إشعارات SimpleX Chat ، ولكن ليس محتوى الرسالة أو حجمها أو مصدرها.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -1528,8 +1523,8 @@
<source>Image will be received when your contact is online, please wait or check later!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -1926,8 +1921,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -1978,8 +1973,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -2010,8 +2005,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -2590,8 +2585,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The app can notify you when you receive messages or contact requests - please open settings to enable." xml:space="preserve">
@@ -2622,8 +2617,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>The microphone does not work when the app is in the background.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -2686,8 +2681,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>To prevent the call interruption, enable Do Not Disturb mode.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -2972,10 +2967,6 @@ To connect, please ask your contact to create another connection link and check
<source>You can use markdown to format messages:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
@@ -3752,8 +3743,8 @@ SimpleX servers cannot see your profile.</source>
<source>%u messages skipped.</source>
<target state="translated">%u تم تخطي الرسائل.</target>
</trans-unit>
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve" approved="no">
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
<trans-unit id="**Create 1-time link**: to create and share a new invitation link." xml:space="preserve" approved="no">
<source>**Create 1-time link**: to create and share a new invitation link.</source>
<target state="translated">**إضافة جهة اتصال**: لإنشاء رابط دعوة جديد، أو الاتصال عبر الرابط الذي تلقيتوهم.</target>
</trans-unit>
<trans-unit id="**Create group**: to create a new group." xml:space="preserve" approved="no">
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
@@ -193,20 +193,16 @@
<source>)</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
<source>**Create link / QR code** for your contact to use.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
@@ -217,8 +213,8 @@
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
@@ -1899,8 +1895,8 @@
<source>Immediately</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -2239,8 +2235,8 @@
<source>Migration is completed</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrations: %@" xml:space="preserve">
<source>Migrations: %@</source>
<trans-unit id="Migrations:" xml:space="preserve">
<source>Migrations:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Moderate" xml:space="preserve">
@@ -2409,8 +2405,8 @@
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -2477,8 +2473,8 @@
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening database…" xml:space="preserve">
@@ -2537,8 +2533,8 @@
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -3373,8 +3369,8 @@
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The ID of the next message is incorrect (less or equal to the previous).&#10;It can happen because of some bug or when the connection is compromised." xml:space="preserve">
@@ -3418,8 +3414,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>The message will be marked as moderated for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -3490,8 +3486,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>To make a new connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -3876,10 +3872,6 @@ To connect, please ask your contact to create another connection link and check
<source>You can't send messages!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
@@ -186,20 +186,16 @@ Available in v5.1</source>
<source>)</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
<source>**Create link / QR code** for your contact to use.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
@@ -210,8 +206,8 @@ Available in v5.1</source>
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
@@ -1708,8 +1704,8 @@ Available in v5.1</source>
<source>Immediately</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -2016,8 +2012,8 @@ Available in v5.1</source>
<source>Migration is completed</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrations: %@" xml:space="preserve">
<source>Migrations: %@</source>
<trans-unit id="Migrations:" xml:space="preserve">
<source>Migrations:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Moderate" xml:space="preserve">
@@ -2174,8 +2170,8 @@ Available in v5.1</source>
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -2234,8 +2230,8 @@ Available in v5.1</source>
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -2290,8 +2286,8 @@ Available in v5.1</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -2994,8 +2990,8 @@ Available in v5.1</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The ID of the next message is incorrect (less or equal to the previous).&#10;It can happen because of some bug or when the connection is compromised." xml:space="preserve">
@@ -3039,8 +3035,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>The message will be marked as moderated for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -3111,8 +3107,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>To make a new connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -3478,10 +3474,6 @@ SimpleX Lock must be enabled.</source>
<source>You can't send messages!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
@@ -217,23 +217,18 @@ Available in v5.1</source>
<target state="translated">)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="translated">**הוסיפו איש קשר חדש**: ליצירת קוד QR או קישור חד־פעמיים עבור איש הקשר שלכם.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve" approved="no">
<source>**Create link / QR code** for your contact to use.</source>
<target state="translated">**צור קישור / קוד QR** לשימוש איש הקשר שלך.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<target state="translated">**יותר פרטי**: בדוק הודעות חדשות כל 20 דקות. אסימון המכשיר משותף עם שרת SimpleX Chat, אך לא כמה אנשי קשר או הודעות יש לך.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<target state="translated">**הכי פרטי**: אל תשתמש בשרת ההתראות של SimpleX Chat, בדוק הודעות מעת לעת ברקע (תלוי בתדירות השימוש באפליקציה).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -247,8 +242,8 @@ Available in v5.1</source>
<target state="translated">**שימו לב**: לא ניתן יהיה לשחזר או לשנות את הסיסמה אם תאבדו אותה.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<target state="translated">**מומלץ**: אסימון מכשיר והתראות נשלחים לשרת ההתראות של SimpleX Chat, אך לא תוכן ההודעה, גודלה או ממי היא.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2115,8 +2110,8 @@ Available in v5.1</source>
<target state="translated">מיד</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve" approved="no">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve" approved="no">
<source>Immune to spam</source>
<target state="translated">חסין מפני ספאם ושימוש לרעה</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2502,9 +2497,9 @@ Available in v5.1</source>
<target state="translated">ההעברה הושלמה</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrations: %@" xml:space="preserve" approved="no">
<source>Migrations: %@</source>
<target state="translated">העברות: %@</target>
<trans-unit id="Migrations:" xml:space="preserve" approved="no">
<source>Migrations:</source>
<target state="translated">העברות:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Moderate" xml:space="preserve" approved="no">
@@ -2701,8 +2696,8 @@ Available in v5.1</source>
<target state="translated">לא ייעשה שימוש במארחי Onion.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -2761,8 +2756,8 @@ Available in v5.1</source>
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -2817,8 +2812,8 @@ Available in v5.1</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -3521,8 +3516,8 @@ Available in v5.1</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The ID of the next message is incorrect (less or equal to the previous).&#10;It can happen because of some bug or when the connection is compromised." xml:space="preserve">
@@ -3566,8 +3561,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>The message will be marked as moderated for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -3638,8 +3633,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>To make a new connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -4005,10 +4000,6 @@ SimpleX Lock must be enabled.</source>
<source>You can't send messages!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
@@ -181,23 +181,18 @@
<source>)</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="needs-translation">**Dodajte novi kontakt**: da biste stvorili svoj jednokratni QR kôd ili vezu za svoj kontakt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve" approved="no">
<source>**Create link / QR code** for your contact to use.</source>
<target state="needs-translation">**Stvorite vezu / QR kôd** za vaš kontakt.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<target state="needs-translation">**Privatnije**: provjeravajte nove poruke svakih 20 minuta. Token uređaja dijeli se s SimpleX Chat poslužiteljem, ali ne i s brojem kontakata ili poruka koje imate.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<target state="needs-translation">**Najprivatniji**: nemojte koristiti SimpleX Chat poslužitelj obavijesti, povremeno provjeravajte poruke u pozadini (ovisi o tome koliko često koristite aplikaciju).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -211,8 +206,8 @@
<target state="needs-translation">**Imajte na umu**: NEĆETE moći oporaviti ili promijeniti pristupni izraz ako ga izgubite.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<target state="needs-translation">**Preporučeno**: token uređaja i obavijesti šalju se na poslužitelj obavijesti SimpleX Chata, ali ne i sadržaj poruke, veličinu ili od koga je.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -1519,8 +1514,8 @@
<source>Image will be received when your contact is online, please wait or check later!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -1917,8 +1912,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -1965,8 +1960,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Open chat console</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -1997,8 +1992,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -2577,8 +2572,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The app can notify you when you receive messages or contact requests - please open settings to enable." xml:space="preserve">
@@ -2609,8 +2604,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>The microphone does not work when the app is in the background.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -2673,8 +2668,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>To prevent the call interruption, enable Do Not Disturb mode.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -2959,10 +2954,6 @@ To connect, please ask your contact to create another connection link and check
<source>You can use markdown to format messages:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
@@ -152,20 +152,16 @@
<source>)</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
<source>**Create link / QR code** for your contact to use.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
@@ -176,8 +172,8 @@
<source>**Please note**: you will NOT be able to recover or change passphrase if you lose it.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
@@ -1537,8 +1533,8 @@
<source>Image will be received when your contact is online, please wait or check later!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -1961,8 +1957,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -2013,8 +2009,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -2049,8 +2045,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -2670,8 +2666,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The app can notify you when you receive messages or contact requests - please open settings to enable." xml:space="preserve">
@@ -2706,8 +2702,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>The message will be marked as moderated for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -2774,8 +2770,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>To make a new connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -3093,10 +3089,6 @@ SimpleX Lock must be enabled.</source>
<source>You can't send messages!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
@@ -162,20 +162,16 @@
<target state="translated">)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
<source>**Create link / QR code** for your contact to use.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Paste received link** or open it in the browser and tap **Open in mobile app**." xml:space="preserve">
@@ -187,8 +183,8 @@
<target state="translated">**Turėkite omenyje**: jeigu prarasite slaptafrazę, NEBEGALĖSITE jos atkurti ar pakeisti.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Scan QR code**: to connect to your contact in person or via video call." xml:space="preserve">
@@ -1513,8 +1509,8 @@
<source>Image will be received when your contact is online, please wait or check later!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -1919,8 +1915,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -1971,8 +1967,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -2003,8 +1999,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -2591,8 +2587,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The app can notify you when you receive messages or contact requests - please open settings to enable." xml:space="preserve">
@@ -2627,8 +2623,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>The message will be marked as moderated for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -2687,8 +2683,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>To make a new connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -2993,10 +2989,6 @@ To connect, please ask your contact to create another connection link and check
<source>You can't send messages!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
@@ -187,23 +187,18 @@
<target state="translated">)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="translated">**Adicionar novo contato**: para criar seu QR Code ou link único para seu contato.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve" approved="no">
<source>**Create link / QR code** for your contact to use.</source>
<target state="translated">**Crie um link / QR code** para seu contato usar.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<target state="translated">**Mais privado**: verifique as novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor do SimpleX Chat, mas não quantos contatos ou mensagens você tem.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<target state="translated">**Mais privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -217,8 +212,8 @@
<target state="translated">**Observação**: NÃO será possível recuperar ou alterar a frase secreta se você a perder.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<target state="translated">**Recomendado**: o token do dispositivo e as notificações são enviados para o servidor de notificações do SimpleX Chat, mas não o conteúdo, o tamanho ou o remetente da mensagem.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -1761,8 +1756,8 @@
<target state="translated">A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve" approved="no">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve" approved="no">
<source>Immune to spam</source>
<target state="translated">Imune a spam e abuso</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2209,8 +2204,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">Hosts Onion não serão usados.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve" approved="no">
@@ -2267,8 +2262,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">Abrir console de chat</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve" approved="no">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve" approved="no">
<source>Anybody can host servers.</source>
<target state="translated">Protocolo de código aberto qualquer um pode executar os servidores.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2306,8 +2301,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">Cole o link que você recebeu na caixa abaixo para conectar com o seu contato.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve" approved="no">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve" approved="no">
<source>You decide who can connect.</source>
<target state="translated">Pessoas podem se conectar com você somente via links compartilhados.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2961,8 +2956,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>Thank you for installing SimpleX Chat!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve" approved="no">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve" approved="no">
<source>No user identifiers.</source>
<target state="translated">A 1ª plataforma sem nenhum identificador de usuário privada por design.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2998,8 +2993,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>The microphone does not work when the app is in the background.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve" approved="no">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve" approved="no">
<source>The future of messaging</source>
<target state="translated">A próxima geração de mensageiros privados</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -3071,8 +3066,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>To prevent the call interruption, enable Do Not Disturb mode.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect your information, turn on SimpleX Lock.&#10;You will be prompted to complete authentication before this feature is enabled." xml:space="preserve" approved="no">
@@ -3402,10 +3397,6 @@ Para se conectar, peça ao seu contato para criar outro link de conexão e verif
<target state="translated">Você pode usar markdown para formatar mensagens:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve" approved="no">
<source>You could not be verified; please try again.</source>
<target state="translated">Você não pôde ser verificado; por favor, tente novamente.</target>
@@ -5482,8 +5473,8 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi
<source>(this device v%@)</source>
<target state="translated">este dispositivo</target>
</trans-unit>
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve" approved="no">
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
<trans-unit id="**Create 1-time link**: to create and share a new invitation link." xml:space="preserve" approved="no">
<source>**Create 1-time link**: to create and share a new invitation link.</source>
<target state="translated">**Adicionar contato**: criar um novo link de convite ou conectar via um link que você recebeu.</target>
</trans-unit>
<trans-unit id="**Create group**: to create a new group." xml:space="preserve" approved="no">
@@ -5570,6 +5561,90 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi
<source>Capacity exceeded - recipient did not receive previously sent messages.</source>
<target state="translated">Capacidade excedida - o destinatário não recebeu as mensagens enviadas anteriormente.</target>
</trans-unit>
<trans-unit id="Chat migrated!" xml:space="preserve" approved="no">
<source>Chat migrated!</source>
<target state="translated">Conversa migrada!</target>
</trans-unit>
<trans-unit id="Auto-accept settings" xml:space="preserve" approved="no">
<source>Auto-accept settings</source>
<target state="translated">Aceitar automaticamente configurações</target>
</trans-unit>
<trans-unit id="App encrypts new local files (except videos)." xml:space="preserve" approved="no">
<source>App encrypts new local files (except videos).</source>
<target state="translated">O aplicativo criptografa novos arquivos locais (exceto videos).</target>
</trans-unit>
<trans-unit id="App session" xml:space="preserve" approved="no">
<source>App session</source>
<target state="translated">Sessão do aplicativo</target>
</trans-unit>
<trans-unit id="Acknowledged" xml:space="preserve" approved="no">
<source>Acknowledged</source>
<target state="translated">Reconhecido</target>
</trans-unit>
<trans-unit id="Acknowledgement errors" xml:space="preserve" approved="no">
<source>Acknowledgement errors</source>
<target state="translated">Erros conhecidos</target>
</trans-unit>
<trans-unit id="Chat list" xml:space="preserve" approved="no">
<source>Chat list</source>
<target state="translated">Lista de conversas</target>
</trans-unit>
<trans-unit id="Chat database exported" xml:space="preserve" approved="no">
<source>Chat database exported</source>
<target state="translated">Banco de dados da conversa exportado</target>
</trans-unit>
<trans-unit id="Chat preferences were changed." xml:space="preserve" approved="no">
<source>Chat preferences were changed.</source>
<target state="translated">As preferências de bate-papo foram alteradas.</target>
</trans-unit>
<trans-unit id="Chat theme" xml:space="preserve" approved="no">
<source>Chat theme</source>
<target state="translated">Tema da conversa</target>
</trans-unit>
<trans-unit id="Better calls" xml:space="preserve" approved="no">
<source>Better calls</source>
<target state="translated">Chamadas melhores</target>
</trans-unit>
<trans-unit id="Better user experience" xml:space="preserve" approved="no">
<source>Better user experience</source>
<target state="translated">Melhor experiência do usuário</target>
</trans-unit>
<trans-unit id="Allow downgrade" xml:space="preserve" approved="no">
<source>Allow downgrade</source>
<target state="translated">Permitir redução</target>
</trans-unit>
<trans-unit id="Additional secondary" xml:space="preserve" approved="no">
<source>Additional secondary</source>
<target state="translated">Secundária adicional</target>
</trans-unit>
<trans-unit id="App data migration" xml:space="preserve" approved="no">
<source>App data migration</source>
<target state="translated">Migração de dados do aplicativo</target>
</trans-unit>
<trans-unit id="Archive and upload" xml:space="preserve" approved="no">
<source>Archive and upload</source>
<target state="translated">Arquivar e enviar</target>
</trans-unit>
<trans-unit id="Background" xml:space="preserve" approved="no">
<source>Background</source>
<target state="translated">Fundo</target>
</trans-unit>
<trans-unit id="Better message dates." xml:space="preserve" approved="no">
<source>Better message dates.</source>
<target state="translated">Datas de mensagens melhores.</target>
</trans-unit>
<trans-unit id="Better notifications" xml:space="preserve" approved="no">
<source>Better notifications</source>
<target state="translated">Notificações melhores</target>
</trans-unit>
<trans-unit id="Better security ✅" xml:space="preserve" approved="no">
<source>Better security ✅</source>
<target state="translated">Melhor segurança ✅</target>
</trans-unit>
<trans-unit id="Chat profile" xml:space="preserve" approved="no">
<source>Chat profile</source>
<target state="translated">Perfil da conversa</target>
</trans-unit>
</body>
</file>
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="pt-BR" datatype="plaintext">
@@ -214,22 +214,17 @@ Available in v5.1</source>
<source>)</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="translated">**Adicionar novo contato**: para criar seu QR Code único ou link para seu contato.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve">
<source>**Create link / QR code** for your contact to use.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<target state="translated">**Mais privado**: verifique novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor SimpleX Chat, mas não com quantos contatos ou mensagens você possui.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<target state="translated">**Totalmente privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo).</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -242,8 +237,8 @@ Available in v5.1</source>
<target state="translated">**Atenção**: Você NÃO poderá recuperar ou alterar a senha caso a perca.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<target state="translated">**Recomendado**: O token do dispositivo e as notificações são enviados ao servidor de notificação do SimpleX Chat, mas não o conteúdo, o tamanho da mensagem ou de quem ela é.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -1812,8 +1807,8 @@ Available in v5.1</source>
<source>Immediately</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve">
<source>Immune to spam</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Import" xml:space="preserve">
@@ -2120,8 +2115,8 @@ Available in v5.1</source>
<source>Migration is completed</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrations: %@" xml:space="preserve">
<source>Migrations: %@</source>
<trans-unit id="Migrations:" xml:space="preserve">
<source>Migrations:</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Moderate" xml:space="preserve">
@@ -2278,8 +2273,8 @@ Available in v5.1</source>
<source>Onion hosts will not be used.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only group owners can change group preferences." xml:space="preserve">
@@ -2338,8 +2333,8 @@ Available in v5.1</source>
<source>Open user profiles</source>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve">
<source>Anybody can host servers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red." xml:space="preserve">
@@ -2394,8 +2389,8 @@ Available in v5.1</source>
<source>Paste the link you received into the box below to connect with your contact.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve">
<source>You decide who can connect.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Periodically" xml:space="preserve">
@@ -3098,8 +3093,8 @@ Available in v5.1</source>
<source>Thanks to the users contribute via Weblate!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve">
<source>No user identifiers.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The ID of the next message is incorrect (less or equal to the previous).&#10;It can happen because of some bug or when the connection is compromised." xml:space="preserve">
@@ -3143,8 +3138,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>The message will be marked as moderated for all members.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve">
<source>The future of messaging</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The old database was not removed during the migration, it can be deleted." xml:space="preserve">
@@ -3215,8 +3210,8 @@ It can happen because of some bug or when the connection is compromised.</source
<source>To make a new connection</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
@@ -3582,10 +3577,6 @@ SimpleX Lock must be enabled.</source>
<source>You can't send messages!</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve">
<source>You could not be verified; please try again.</source>
<note>No comment provided by engineer.</note>
@@ -4302,8 +4293,8 @@ SimpleX servers cannot see your profile.</source>
<target state="translated">%lld novas interface de idiomas</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add contact**: to create a new invitation link, or connect via a link you received." xml:space="preserve" approved="no">
<source>**Add contact**: to create a new invitation link, or connect via a link you received.</source>
<trans-unit id="**Create 1-time link**: to create and share a new invitation link." xml:space="preserve" approved="no">
<source>**Create 1-time link**: to create and share a new invitation link.</source>
<target state="translated">**Adicionar contato**: para criar um novo link de convite ou conectar-se por meio de um link que você recebeu.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";
@@ -187,23 +187,18 @@
<target state="translated">)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Add new contact**: to create your one-time QR Code for your contact." xml:space="preserve" approved="no">
<source>**Add new contact**: to create your one-time QR Code or link for your contact.</source>
<target state="translated">**新增新的聯絡人**:建立一次性二維碼或連結連接聯絡人。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Create link / QR code** for your contact to use." xml:space="preserve" approved="no">
<source>**Create link / QR code** for your contact to use.</source>
<target state="translated">**建立連結 / 二維碼** 讓你的聯絡人使用。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have.</source>
<trans-unit id="**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." xml:space="preserve" approved="no">
<source>**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata.</source>
<target state="translated">**更有私隱**:每20分鐘會檢查一次訊息。裝置權杖與 SimpleX Chat 伺服器分享中,但是不包括你的聯絡人和訊息資料。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app).</source>
<trans-unit id="**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." xml:space="preserve" approved="no">
<source>**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app.</source>
<target state="translated">**最有私隱**:不使用 SimpleX Chat 通知服務器,在後台定期檢查訊息(取決於你使用應用程序的頻率)。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -217,8 +212,8 @@
<target state="translated">**請注意**:如果你忘記了密碼你將不能再次復原或更改密碼。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from.</source>
<trans-unit id="**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." xml:space="preserve" approved="no">
<source>**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from.</source>
<target state="translated">**建議**:裝置權杖和通知都會傳送去 SimpeleX Chat 的通知伺服器,但是不包括訊息內容、大小或傳送者資料。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -1747,8 +1742,8 @@
<target state="translated">下載圖片需要傳送者上線的時候才能下載圖片,請等待對方上線!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Immune to spam and abuse" xml:space="preserve" approved="no">
<source>Immune to spam and abuse</source>
<trans-unit id="Immune to spam" xml:space="preserve" approved="no">
<source>Immune to spam</source>
<target state="translated">不受垃圾郵件和濫用行為影響</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2217,8 +2212,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">Onion 主機不會啟用。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve" approved="no">
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages." xml:space="preserve" approved="no">
<source>Only client devices store user profiles, contacts, groups, and messages.</source>
<target state="translated">只有客戶端裝置才會儲存你的個人檔案、聯絡人,群組,所有訊息都會經過**兩層的端對端加密**。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2277,8 +2272,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">使用終端機開啟對話</target>
<note>authentication reason</note>
</trans-unit>
<trans-unit id="Open-source protocol and code anybody can run the servers." xml:space="preserve" approved="no">
<source>Open-source protocol and code anybody can run the servers.</source>
<trans-unit id="Anybody can host servers." xml:space="preserve" approved="no">
<source>Anybody can host servers.</source>
<target state="translated">開放源碼協議和程式碼 – 任何人也可以運行伺服器。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -2317,8 +2312,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">將你接收到的連結貼上至下面的框內,以開始你與你的聯絡人對話。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="People can connect to you only via the links you share." xml:space="preserve" approved="no">
<source>People can connect to you only via the links you share.</source>
<trans-unit id="You decide who can connect." xml:space="preserve" approved="no">
<source>You decide who can connect.</source>
<target state="translated">人們只能在你分享了連結後,才能和你連接。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -3010,8 +3005,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<target state="translated">感謝你安裝SimpleX Chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The 1st platform without any user identifiers private by design." xml:space="preserve" approved="no">
<source>The 1st platform without any user identifiers private by design.</source>
<trans-unit id="No user identifiers." xml:space="preserve" approved="no">
<source>No user identifiers.</source>
<target state="translated">第一個沒有任何用戶識別符的通訊平台 – 以私隱為設計。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -3049,8 +3044,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>The microphone does not work when the app is in the background.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The next generation of private messaging" xml:space="preserve" approved="no">
<source>The next generation of private messaging</source>
<trans-unit id="The future of messaging" xml:space="preserve" approved="no">
<source>The future of messaging</source>
<target state="translated">新一代的私密訊息平台</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -3118,8 +3113,8 @@ We will be adding server redundancy to prevent lost messages.</source>
<source>To prevent the call interruption, enable Do Not Disturb mode.</source>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve" approved="no">
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
<trans-unit id="To protect your privacy, SimpleX uses separate IDs for each of your contacts." xml:space="preserve" approved="no">
<source>To protect your privacy, SimpleX uses separate IDs for each of your contacts.</source>
<target state="translated">為了保護隱私,而不像是其他平台般需要提取和存儲用戶的 IDs 資料,SimpleX 平台有自家佇列的標識符,這對於你的每個聯絡人也是獨一無二的。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
@@ -3455,11 +3450,6 @@ To connect, please ask your contact to create another connection link and check
<target state="translated">你可以使用 Markdown 語法以更清楚標明訊息:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." xml:space="preserve" approved="no">
<source>You control through which server(s) **to receive** the messages, your contacts the servers you use to message them.</source>
<target state="translated">你可以控制通過哪一個伺服器 **來接收** 你的聯絡人訊息 – 這些伺服器用來接收他們傳送給你的訊息。</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You could not be verified; please try again." xml:space="preserve" approved="no">
<source>You could not be verified; please try again.</source>
<target state="translated">你未能通過認證;請再試一次。</target>
@@ -4791,8 +4781,8 @@ Available in v5.1</source>
<target state="translated">訊息 &amp; 檔案</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Migrations: %@" xml:space="preserve" approved="no">
<source>Migrations: %@</source>
<trans-unit id="Migrations:" xml:space="preserve" approved="no">
<source>Migrations:</source>
<target state="translated">遷移:%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
+39 -22
View File
@@ -139,7 +139,6 @@
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; };
5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; };
@@ -149,11 +148,11 @@
6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; };
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; };
642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */; };
642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */; };
642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82F2CEB3D4B005E9412 /* libffi.a */; };
642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8302CEB3D4B005E9412 /* libgmp.a */; };
642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8312CEB3D4B005E9412 /* libgmpxx.a */; };
642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */; };
642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */; };
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; };
6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; };
@@ -204,6 +203,8 @@
8C8118722C220B5B00E6FC94 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 8C8118712C220B5B00E6FC94 /* Yams */; };
8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; };
8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; };
8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 8CB3476B2CF5CFFA006787A5 /* Ink */; };
8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */; };
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; };
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; };
8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; };
@@ -492,7 +493,6 @@
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = "<group>"; };
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = "<group>"; };
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = "<group>"; };
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = "<group>"; };
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = "<group>"; };
640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
@@ -501,11 +501,11 @@
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = "<group>"; };
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = "<group>"; };
642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a"; sourceTree = "<group>"; };
642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a"; sourceTree = "<group>"; };
642BA82F2CEB3D4B005E9412 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
642BA8302CEB3D4B005E9412 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
642BA8312CEB3D4B005E9412 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a"; sourceTree = "<group>"; };
642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a"; sourceTree = "<group>"; };
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = "<group>"; };
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
@@ -557,6 +557,7 @@
8C852B072C1086D100BA61E8 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
8C86EBE42C0DAE4F00E12243 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeModeEditor.swift; sourceTree = "<group>"; };
8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsWebView.swift; sourceTree = "<group>"; };
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = "<group>"; };
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; };
8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = "<group>"; };
@@ -646,6 +647,7 @@
files = (
5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */,
8C8118722C220B5B00E6FC94 /* Yams in Frameworks */,
8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */,
D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */,
D7197A1829AE89660055C05A /* WebRTC in Frameworks */,
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */,
@@ -680,8 +682,8 @@
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */,
642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */,
642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a in Frameworks */,
642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a in Frameworks */,
642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a in Frameworks */,
642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a in Frameworks */,
642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -762,8 +764,8 @@
642BA82F2CEB3D4B005E9412 /* libffi.a */,
642BA8302CEB3D4B005E9412 /* libgmp.a */,
642BA8312CEB3D4B005E9412 /* libgmpxx.a */,
642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */,
642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */,
642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */,
642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -1064,7 +1066,6 @@
isa = PBXGroup;
children = (
5C4B3B09285FB130003915F2 /* DatabaseView.swift */,
5CFA59CF286477B400863A68 /* ChatArchiveView.swift */,
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */,
5C9CC7A828C532AB00BEF955 /* DatabaseErrorView.swift */,
5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */,
@@ -1082,6 +1083,7 @@
643B3B4D2CCFD6400083A2CF /* OperatorView.swift */,
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */,
8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */,
);
path = NetworkAndServers;
sourceTree = "<group>";
@@ -1193,6 +1195,7 @@
D7F0E33829964E7E0068AF69 /* LZString */,
D7197A1729AE89660055C05A /* WebRTC */,
8C8118712C220B5B00E6FC94 /* Yams */,
8CB3476B2CF5CFFA006787A5 /* Ink */,
);
productName = "SimpleX (iOS)";
productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */;
@@ -1336,6 +1339,7 @@
D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */,
D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */,
8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */,
8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */,
);
productRefGroup = 5CA059CB279559F40002BEB4 /* Products */;
projectDirPath = "";
@@ -1514,7 +1518,6 @@
8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */,
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */,
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */,
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */,
CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */,
@@ -1526,6 +1529,7 @@
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */,
8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */,
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */,
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
@@ -1937,7 +1941,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1986,7 +1990,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2027,7 +2031,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2047,7 +2051,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2072,7 +2076,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2109,7 +2113,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2146,7 +2150,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2197,7 +2201,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2248,7 +2252,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2282,7 +2286,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 246;
CURRENT_PROJECT_VERSION = 247;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2385,6 +2389,14 @@
version = 5.1.2;
};
};
8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnsundell/ink";
requirement = {
kind = exactVersion;
version = 0.6.0;
};
};
D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/simplex-chat/WebRTC.git";
@@ -2422,6 +2434,11 @@
package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */;
productName = Yams;
};
8CB3476B2CF5CFFA006787A5 /* Ink */ = {
isa = XCSwiftPackageProductDependency;
package = 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */;
productName = Ink;
};
CE38A29B2C3FCD72005ED185 /* SwiftyGif */ = {
isa = XCSwiftPackageProductDependency;
package = D77B92DA2952372200A5A1CC /* XCRemoteSwiftPackageReference "SwiftyGif" */;
@@ -1,5 +1,5 @@
{
"originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115",
"originHash" : "33afc44be5f4225325b3cb940ed71b6cbf3ef97290d348d7b6803697bcd0637d",
"pins" : [
{
"identity" : "codescanner",
@@ -10,6 +10,15 @@
"version" : "2.5.0"
}
},
{
"identity" : "ink",
"kind" : "remoteSourceControl",
"location" : "https://github.com/johnsundell/ink",
"state" : {
"revision" : "bcc9f219900a62c4210e6db726035d7f03ae757b",
"version" : "0.6.0"
}
},
{
"identity" : "lzstring-swift",
"kind" : "remoteSourceControl",
+19 -5
View File
@@ -49,6 +49,7 @@ public enum ChatCommand {
case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode)
case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64])
case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction)
case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction)
case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64])
case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?)
case apiGetNtfToken
@@ -137,7 +138,7 @@ public enum ChatCommand {
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
// WebRTC calls /
case apiGetNetworkStatuses
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
case apiChatRead(type: ChatType, id: Int64)
case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64])
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?)
@@ -212,6 +213,7 @@ public enum ChatCommand {
case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))"
case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))"
case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl):
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
@@ -310,7 +312,7 @@ public enum ChatCommand {
case .apiGetCallInvitations: return "/_call get"
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
case .apiGetNetworkStatuses: return "/_network_statuses"
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))"
case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))"
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
@@ -375,6 +377,7 @@ public enum ChatCommand {
case .apiConnectContactViaAddress: return "apiConnectContactViaAddress"
case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem"
case .apiChatItemReaction: return "apiChatItemReaction"
case .apiGetReactionMembers: return "apiGetReactionMembers"
case .apiPlanForwardChatItems: return "apiPlanForwardChatItems"
case .apiForwardChatItems: return "apiForwardChatItems"
case .apiGetNtfToken: return "apiGetNtfToken"
@@ -629,6 +632,7 @@ public enum ChatResponse: Decodable, Error {
case chatItemUpdated(user: UserRef, chatItem: AChatItem)
case chatItemNotChanged(user: UserRef, chatItem: AChatItem)
case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction)
case reactionMembers(user: UserRef, memberReactions: [MemberReaction])
case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool)
case contactsList(user: UserRef, contacts: [Contact])
// group events
@@ -805,6 +809,7 @@ public enum ChatResponse: Decodable, Error {
case .chatItemUpdated: return "chatItemUpdated"
case .chatItemNotChanged: return "chatItemNotChanged"
case .chatItemReaction: return "chatItemReaction"
case .reactionMembers: return "reactionMembers"
case .chatItemsDeleted: return "chatItemsDeleted"
case .contactsList: return "contactsList"
case .groupCreated: return "groupCreated"
@@ -983,6 +988,7 @@ public enum ChatResponse: Decodable, Error {
case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem))
case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem))
case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))")
case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))")
case let .chatItemsDeleted(u, items, byUser):
let itemsString = items.map { item in
"deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n")
@@ -2158,9 +2164,17 @@ public enum NotificationsMode: String, Decodable, SelectableItem {
public var label: LocalizedStringKey {
switch self {
case .off: "Local"
case .periodic: "Periodically"
case .instant: "Instantly"
case .off: "No push server"
case .periodic: "Periodic"
case .instant: "Instant"
}
}
public var icon: String {
switch self {
case .off: return "arrow.clockwise"
case .periodic: return "timer"
case .instant: return "bolt"
}
}
+5
View File
@@ -2311,6 +2311,11 @@ public struct ACIReaction: Decodable, Hashable {
public var chatReaction: CIReaction
}
public struct MemberReaction: Decodable, Hashable {
public var groupMember: GroupMember
public var reactionTs: Date
}
public struct CIReaction: Decodable, Hashable {
public var chatDir: CIDirection
public var chatItem: ChatItem
+1 -1
View File
@@ -138,7 +138,7 @@ private func reduceSize(_ image: UIImage, ratio: CGFloat, hasAlpha: Bool) -> UII
return resizeImage(image, newBounds: bounds, drawIn: bounds, hasAlpha: hasAlpha)
}
private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage {
public func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1.0
format.opaque = !hasAlpha
+17
View File
@@ -63,6 +63,23 @@ extension Color {
)
}
public func toHTMLHex() -> String {
let uiColor: UIColor = .init(self)
var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
// Can be negative values and more than 1. Extended color range, making it normal
r = min(1, max(0, r))
g = min(1, max(0, g))
b = min(1, max(0, b))
a = min(1, max(0, a))
return String(format: "#%02x%02x%02x%02x",
Int((r * 255).rounded()),
Int((g * 255).rounded()),
Int((b * 255).rounded()),
Int((a * 255).rounded())
)
}
public func darker(_ factor: CGFloat = 0.1) -> Color {
var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a)
+46 -145
View File
@@ -1,15 +1,6 @@
/* No comment provided by engineer. */
"\n" = "\n";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" (" = " (";
@@ -65,10 +56,7 @@
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Звезда в GitHub](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */
"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Добави контакт**: за създаване на нов линк или свързване чрез получен линк за връзка.";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Добави нов контакт**: за да създадете своя еднократен QR код или линк за вашия контакт.";
"**Create 1-time link**: to create and share a new invitation link." = "**Добави контакт**: за създаване на нов линк.";
/* No comment provided by engineer. */
"**Create group**: to create a new group." = "**Създай група**: за създаване на нова група.";
@@ -80,10 +68,10 @@
"**e2e encrypted** video call" = "**e2e криптирано** видео разговор";
/* No comment provided by engineer. */
"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате.";
"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате.";
/* No comment provided by engineer. */
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението).";
"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението).";
/* No comment provided by engineer. */
"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Моля, обърнете внимание**: използването на една и съща база данни на две устройства ще наруши декриптирането на съобщенията от вашите връзки като защита на сигурността.";
@@ -92,7 +80,7 @@
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите.";
/* No comment provided by engineer. */
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е.";
"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е.";
/* No comment provided by engineer. */
"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: Незабавните push известия изискват парола, запазена в Keychain.";
@@ -322,12 +310,6 @@
/* No comment provided by engineer. */
"Abort changing address?" = "Откажи смяна на адрес?";
/* No comment provided by engineer. */
"About SimpleX" = "За SimpleX";
/* No comment provided by engineer. */
"About SimpleX address" = "Повече за SimpleX адреса";
/* No comment provided by engineer. */
"About SimpleX Chat" = "За SimpleX Chat";
@@ -355,12 +337,6 @@
/* No comment provided by engineer. */
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти.";
/* No comment provided by engineer. */
"Add contact" = "Добави контакт";
/* No comment provided by engineer. */
"Add preset servers" = "Добави предварително зададени сървъри";
/* No comment provided by engineer. */
"Add profile" = "Добави профил";
@@ -517,6 +493,9 @@
/* No comment provided by engineer. */
"Answer call" = "Отговор на повикване";
/* No comment provided by engineer. */
"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри.";
/* No comment provided by engineer. */
"App build: %@" = "Компилация на приложението: %@";
@@ -697,7 +676,8 @@
/* No comment provided by engineer. */
"Can't invite contacts!" = "Не може да поканят контактите!";
/* alert button */
/* alert action
alert button */
"Cancel" = "Отказ";
/* No comment provided by engineer. */
@@ -761,9 +741,6 @@
/* chat item text */
"changing address…" = "промяна на адреса…";
/* No comment provided by engineer. */
"Chat archive" = "Архив на чата";
/* No comment provided by engineer. */
"Chat console" = "Конзола";
@@ -797,7 +774,7 @@
/* No comment provided by engineer. */
"Chats" = "Чатове";
/* No comment provided by engineer. */
/* alert title */
"Check server address and try again." = "Проверете адреса на сървъра и опитайте отново.";
/* No comment provided by engineer. */
@@ -1019,9 +996,6 @@
/* No comment provided by engineer. */
"Create a group using a random profile." = "Създай група с автоматично генериран профилл.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Създайте адрес, за да позволите на хората да се свързват с вас.";
/* server test step */
"Create file" = "Създай файл";
@@ -1058,9 +1032,6 @@
/* copied message info */
"Created at: %@" = "Създаден на: %@";
/* No comment provided by engineer. */
"Created on %@" = "Създаден на %@";
/* No comment provided by engineer. */
"Creating archive link" = "Създаване на архивен линк";
@@ -1163,7 +1134,8 @@
/* No comment provided by engineer. */
"default (yes)" = "по подразбиране (да)";
/* chat item action
/* alert action
chat item action
swipe action */
"Delete" = "Изтрий";
@@ -1185,12 +1157,6 @@
/* No comment provided by engineer. */
"Delete and notify contact" = "Изтрий и уведоми контакт";
/* No comment provided by engineer. */
"Delete archive" = "Изтрий архив";
/* No comment provided by engineer. */
"Delete chat archive?" = "Изтриване на архива на чата?";
/* No comment provided by engineer. */
"Delete chat profile" = "Изтрий чат профила";
@@ -1603,9 +1569,6 @@
/* No comment provided by engineer. */
"Error accepting contact request" = "Грешка при приемане на заявка за контакт";
/* No comment provided by engineer. */
"Error accessing database file" = "Грешка при достъпа до файла с базата данни";
/* No comment provided by engineer. */
"Error adding member(s)" = "Грешка при добавяне на член(ове)";
@@ -1681,9 +1644,6 @@
/* No comment provided by engineer. */
"Error joining group" = "Грешка при присъединяване към група";
/* No comment provided by engineer. */
"Error loading %@ servers" = "Грешка при зареждане на %@ сървъри";
/* No comment provided by engineer. */
"Error opening chat" = "Грешка при отваряне на чата";
@@ -1693,9 +1653,6 @@
/* No comment provided by engineer. */
"Error removing member" = "Грешка при отстраняване на член";
/* No comment provided by engineer. */
"Error saving %@ servers" = "Грешка при запазване на %@ сървъра";
/* No comment provided by engineer. */
"Error saving group profile" = "Грешка при запазване на профила на групата";
@@ -2029,9 +1986,6 @@
/* time unit */
"hours" = "часове";
/* No comment provided by engineer. */
"How it works" = "Как работи";
/* No comment provided by engineer. */
"How SimpleX works" = "Как работи SimpleX";
@@ -2075,7 +2029,7 @@
"Immediately" = "Веднага";
/* No comment provided by engineer. */
"Immune to spam and abuse" = "Защитен от спам и злоупотреби";
"Immune to spam" = "Защитен от спам и злоупотреби";
/* No comment provided by engineer. */
"Import" = "Импортиране";
@@ -2165,10 +2119,10 @@
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */
"Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n";
"Instant" = "Мигновено";
/* No comment provided by engineer. */
"Instantly" = "Мигновено";
"Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n";
/* No comment provided by engineer. */
"Interface" = "Интерфейс";
@@ -2203,7 +2157,7 @@
/* No comment provided by engineer. */
"Invalid response" = "Невалиден отговор";
/* No comment provided by engineer. */
/* alert title */
"Invalid server address!" = "Невалиден адрес на сървъра!";
/* item status text */
@@ -2299,13 +2253,13 @@
/* No comment provided by engineer. */
"Joining group" = "Присъединяване към групата";
/* No comment provided by engineer. */
/* alert action */
"Keep" = "Запази";
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Дръжте приложението отворено, за да го използвате от настолното устройство";
/* No comment provided by engineer. */
/* alert title */
"Keep unused invitation?" = "Запази неизползваната покана за връзка?";
/* No comment provided by engineer. */
@@ -2362,9 +2316,6 @@
/* No comment provided by engineer. */
"Live messages" = "Съобщения на живо";
/* No comment provided by engineer. */
"Local" = "Локално";
/* No comment provided by engineer. */
"Local name" = "Локално име";
@@ -2377,24 +2328,15 @@
/* No comment provided by engineer. */
"Lock mode" = "Режим на заключване";
/* No comment provided by engineer. */
"Make a private connection" = "Добави поверителна връзка";
/* No comment provided by engineer. */
"Make one message disappear" = "Накарайте едно съобщение да изчезне";
/* 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" = "Маркирай като изтрито за всички";
@@ -2513,7 +2455,7 @@
"Migration is completed" = "Миграцията е завършена";
/* No comment provided by engineer. */
"Migrations: %@" = "Миграции: %@";
"Migrations:" = "Миграции:";
/* time unit */
"minutes" = "минути";
@@ -2587,9 +2529,6 @@
/* notification */
"New contact:" = "Нов контакт:";
/* No comment provided by engineer. */
"New database archive" = "Нов архив на база данни";
/* No comment provided by engineer. */
"New desktop app!" = "Ново настолно приложение!";
@@ -2653,12 +2592,18 @@
/* No comment provided by engineer. */
"No permission to record voice message" = "Няма разрешение за запис на гласово съобщение";
/* No comment provided by engineer. */
"No push server" = "Локално";
/* No comment provided by engineer. */
"No received or sent files" = "Няма получени или изпратени файлове";
/* copied message info in history */
"no text" = "няма текст";
/* No comment provided by engineer. */
"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн.";
/* No comment provided by engineer. */
"Not compatible!" = "Несъвместим!";
@@ -2697,9 +2642,6 @@
/* No comment provided by engineer. */
"Old database" = "Стара база данни";
/* No comment provided by engineer. */
"Old database archive" = "Стар архив на база данни";
/* group pref value */
"on" = "включено";
@@ -2716,7 +2658,7 @@
"Onion hosts will not be used." = "Няма се използват Onion хостове.";
/* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**.";
"Only client devices store user profiles, contacts, groups, and messages." = "Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**.";
/* No comment provided by engineer. */
"Only group owners can change group preferences." = "Само собствениците на групата могат да променят груповите настройки.";
@@ -2775,12 +2717,6 @@
/* No comment provided by engineer. */
"Open Settings" = "Отвори настройки";
/* authentication reason */
"Open user profiles" = "Отвори потребителските профили";
/* No comment provided by engineer. */
"Open-source protocol and code anybody can run the servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри.";
/* No comment provided by engineer. */
"Opening app…" = "Приложението се отваря…";
@@ -2842,10 +2778,7 @@
"peer-to-peer" = "peer-to-peer";
/* No comment provided by engineer. */
"People can connect to you only via the links you share." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте.";
/* No comment provided by engineer. */
"Periodically" = "Периодично";
"Periodic" = "Периодично";
/* message decrypt error item */
"Permanent decryption error" = "Постоянна грешка при декриптиране";
@@ -2910,9 +2843,6 @@
/* No comment provided by engineer. */
"Preserve the last message draft, with attachments." = "Запазете последната чернова на съобщението с прикачени файлове.";
/* No comment provided by engineer. */
"Preset server" = "Предварително зададен сървър";
/* No comment provided by engineer. */
"Preset server address" = "Предварително зададен адрес на сървъра";
@@ -2943,7 +2873,7 @@
/* No comment provided by engineer. */
"Profile password" = "Профилна парола";
/* No comment provided by engineer. */
/* alert message */
"Profile update will be sent to your contacts." = "Актуализацията на профила ще бъде изпратена до вашите контакти.";
/* No comment provided by engineer. */
@@ -3010,10 +2940,10 @@
"Read more" = "Прочетете още";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).";
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
@@ -3021,9 +2951,6 @@
/* No comment provided by engineer. */
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme).";
/* No comment provided by engineer. */
"Read more in our GitHub repository." = "Прочетете повече в нашето хранилище в GitHub.";
/* No comment provided by engineer. */
"Receipts are disabled" = "Потвърждениeто за доставка е деактивирано";
@@ -3221,9 +3148,6 @@
/* No comment provided by engineer. */
"Save and update group profile" = "Запази и актуализирай профила на групата";
/* No comment provided by engineer. */
"Save archive" = "Запази архив";
/* No comment provided by engineer. */
"Save group profile" = "Запази профила на групата";
@@ -3242,7 +3166,7 @@
/* No comment provided by engineer. */
"Save servers" = "Запази сървърите";
/* No comment provided by engineer. */
/* alert title */
"Save servers?" = "Запази сървърите?";
/* No comment provided by engineer. */
@@ -3353,9 +3277,6 @@
/* No comment provided by engineer. */
"Send notifications" = "Изпращай известия";
/* No comment provided by engineer. */
"Send notifications:" = "Изпратени известия:";
/* No comment provided by engineer. */
"Send questions and ideas" = "Изпращайте въпроси и идеи";
@@ -3467,7 +3388,8 @@
/* No comment provided by engineer. */
"Shape profile images" = "Променете формата на профилните изображения";
/* chat item action */
/* alert action
chat item action */
"Share" = "Сподели";
/* No comment provided by engineer. */
@@ -3476,7 +3398,7 @@
/* No comment provided by engineer. */
"Share address" = "Сподели адрес";
/* No comment provided by engineer. */
/* alert title */
"Share address with contacts?" = "Сподели адреса с контактите?";
/* No comment provided by engineer. */
@@ -3590,9 +3512,6 @@
/* No comment provided by engineer. */
"Stop chat" = "Спри чата";
/* No comment provided by engineer. */
"Stop chat to enable database actions" = "Спрете чата, за да активирате действията с базата данни";
/* 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." = "Спрете чата, за да експортирате, импортирате или изтриете чат базата данни. Няма да можете да получавате и изпращате съобщения, докато чатът е спрян.";
@@ -3608,10 +3527,10 @@
/* No comment provided by engineer. */
"Stop sending file?" = "Спри изпращането на файла?";
/* No comment provided by engineer. */
/* alert action */
"Stop sharing" = "Спри споделянето";
/* No comment provided by engineer. */
/* alert title */
"Stop sharing address?" = "Спри споделянето на адреса?";
/* authentication reason */
@@ -3680,7 +3599,7 @@
/* No comment provided by engineer. */
"Test servers" = "Тествай сървърите";
/* No comment provided by engineer. */
/* alert title */
"Tests failed!" = "Тестовете са неуспешни!";
/* No comment provided by engineer. */
@@ -3692,9 +3611,6 @@
/* No comment provided by engineer. */
"Thanks to the users contribute via Weblate!" = "Благодарение на потребителите – допринесете през Weblate!";
/* No comment provided by engineer. */
"The 1st platform without any user identifiers private by design." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн.";
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате.";
@@ -3716,6 +3632,9 @@
/* 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 future of messaging" = "Ново поколение поверителни съобщения";
/* No comment provided by engineer. */
"The hash of the previous message is different." = "Хешът на предишното съобщение е различен.";
@@ -3728,9 +3647,6 @@
/* No comment provided by engineer. */
"The message will be marked as moderated for all members." = "Съобщението ще бъде маркирано като модерирано за всички членове.";
/* No comment provided by engineer. */
"The next generation of private messaging" = "Ново поколение поверителни съобщения";
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита.";
@@ -3806,15 +3722,15 @@
/* No comment provided by engineer. */
"To make a new connection" = "За да направите нова връзка";
/* 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." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти.";
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "За да не се разкрива часовата зона, файловете с изображения/глас използват UTC.";
/* No comment provided by engineer. */
"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "За да защитите информацията си, включете SimpleX заключване.\nЩе бъдете подканени да извършите идентификация, преди тази функция да бъде активирана.";
/* No comment provided by engineer. */
"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти.";
/* No comment provided by engineer. */
"To record voice message please grant permission to use Microphone." = "За да запишете гласово съобщение, моля, дайте разрешение за използване на микрофон.";
@@ -4130,9 +4046,6 @@
/* No comment provided by engineer. */
"When connecting audio and video calls." = "При свързване на аудио и видео разговори.";
/* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите.";
/* 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." = "Когато споделяте инкогнито профил с някого, този профил ще се използва за групите, в които той ви кани.";
@@ -4250,9 +4163,6 @@
/* No comment provided by engineer. */
"You can share this address with your contacts to let them connect with **%@**." = "Можете да споделите този адрес с вашите контакти, за да им позволите да се свържат с **%@**.";
/* No comment provided by engineer. */
"You can share your address as a link or QR code - anybody can connect to you." = "Можете да споделите адреса си като линк или QR код - всеки може да се свърже с вас.";
/* No comment provided by engineer. */
"You can start chat via app Settings / Database or by restarting the app" = "Можете да започнете чат през Настройки на приложението / База данни или като рестартирате приложението";
@@ -4262,7 +4172,7 @@
/* No comment provided by engineer. */
"You can use markdown to format messages:" = "Можете да използвате markdown за форматиране на съобщенията:";
/* No comment provided by engineer. */
/* alert message */
"You can view invitation link again in connection details." = "Можете да видите отново линкът за покана в подробностите за връзката.";
/* No comment provided by engineer. */
@@ -4281,10 +4191,10 @@
"you changed role of %@ to %@" = "променихте ролята на %1$@ на %2$@";
/* No comment provided by engineer. */
"You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." = "Вие контролирате през кой сървър(и) **да получавате** съобщенията, вашите контакти – сървърите, които използвате, за да им изпращате съобщения.";
"You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново.";
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново.";
"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте.";
/* No comment provided by engineer. */
"You have already requested connection via this address!" = "Вече сте заявили връзка през този адрес!";
@@ -4367,9 +4277,6 @@
/* 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" = "Използвате инкогнито профил за тази група - за да се предотврати споделянето на основния ви профил, поканите на контакти не са разрешени";
/* No comment provided by engineer. */
"Your %@ servers" = "Вашите %@ сървъри";
/* No comment provided by engineer. */
"Your calls" = "Вашите обаждания";
@@ -4421,9 +4328,6 @@
/* No comment provided by engineer. */
"Your random profile" = "Вашият автоматично генериран профил";
/* No comment provided by engineer. */
"Your server" = "Вашият сървър";
/* No comment provided by engineer. */
"Your server address" = "Вашият адрес на сървъра";
@@ -4436,6 +4340,3 @@
/* No comment provided by engineer. */
"Your SMP servers" = "Вашите SMP сървъри";
/* No comment provided by engineer. */
"Your XFTP servers" = "Вашите XFTP сървъри";
+41 -137
View File
@@ -1,15 +1,6 @@
/* No comment provided by engineer. */
"\n" = "\n";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" (" = " (";
@@ -55,9 +46,6 @@
/* No comment provided by engineer. */
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt.";
/* No comment provided by engineer. */
"**e2e encrypted** audio call" = "**e2e šifrovaný** audio hovor";
@@ -65,16 +53,16 @@
"**e2e encrypted** video call" = "**e2e šifrovaný** videohovor";
/* No comment provided by engineer. */
"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv.";
"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv.";
/* No comment provided by engineer. */
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte).";
"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte).";
/* No comment provided by engineer. */
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit.";
/* No comment provided by engineer. */
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy.";
"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy.";
/* No comment provided by engineer. */
"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence.";
@@ -274,12 +262,6 @@
/* No comment provided by engineer. */
"Abort changing address?" = "Přerušit změnu adresy?";
/* No comment provided by engineer. */
"About SimpleX" = "O SimpleX";
/* No comment provided by engineer. */
"About SimpleX address" = "O SimpleX adrese";
/* No comment provided by engineer. */
"About SimpleX Chat" = "O SimpleX chat";
@@ -307,9 +289,6 @@
/* No comment provided by engineer. */
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům.";
/* No comment provided by engineer. */
"Add preset servers" = "Přidejte přednastavené servery";
/* No comment provided by engineer. */
"Add profile" = "Přidat profil";
@@ -436,6 +415,9 @@
/* No comment provided by engineer. */
"Answer call" = "Přijmout hovor";
/* No comment provided by engineer. */
"Anybody can host servers." = "Servery může provozovat kdokoli.";
/* No comment provided by engineer. */
"App build: %@" = "Sestavení aplikace: %@";
@@ -562,7 +544,8 @@
/* No comment provided by engineer. */
"Can't invite contacts!" = "Nelze pozvat kontakty!";
/* alert button */
/* alert action
alert button */
"Cancel" = "Zrušit";
/* feature offered item */
@@ -620,9 +603,6 @@
/* chat item text */
"changing address…" = "změna adresy…";
/* No comment provided by engineer. */
"Chat archive" = "Chat se archivuje";
/* No comment provided by engineer. */
"Chat console" = "Konzola pro chat";
@@ -650,7 +630,7 @@
/* No comment provided by engineer. */
"Chats" = "Chaty";
/* No comment provided by engineer. */
/* alert title */
"Check server address and try again." = "Zkontrolujte adresu serveru a zkuste to znovu.";
/* No comment provided by engineer. */
@@ -815,9 +795,6 @@
/* No comment provided by engineer. */
"Create" = "Vytvořit";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Vytvořit adresu, aby se s vámi lidé mohli spojit.";
/* server test step */
"Create file" = "Vytvořit soubor";
@@ -842,9 +819,6 @@
/* No comment provided by engineer. */
"Create your profile" = "Vytvořte si profil";
/* No comment provided by engineer. */
"Created on %@" = "Vytvořeno na %@";
/* No comment provided by engineer. */
"creator" = "tvůrce";
@@ -941,7 +915,8 @@
/* No comment provided by engineer. */
"default (yes)" = "výchozí (ano)";
/* chat item action
/* alert action
chat item action
swipe action */
"Delete" = "Smazat";
@@ -957,12 +932,6 @@
/* No comment provided by engineer. */
"Delete all files" = "Odstranit všechny soubory";
/* No comment provided by engineer. */
"Delete archive" = "Smazat archiv";
/* No comment provided by engineer. */
"Delete chat archive?" = "Smazat archiv chatu?";
/* No comment provided by engineer. */
"Delete chat profile" = "Smazat chat profil";
@@ -1308,9 +1277,6 @@
/* No comment provided by engineer. */
"Error accepting contact request" = "Chyba při přijímání žádosti o kontakt";
/* No comment provided by engineer. */
"Error accessing database file" = "Chyba přístupu k souboru databáze";
/* No comment provided by engineer. */
"Error adding member(s)" = "Chyba přidávání člena(ů)";
@@ -1380,18 +1346,12 @@
/* No comment provided by engineer. */
"Error joining group" = "Chyba při připojování ke skupině";
/* No comment provided by engineer. */
"Error loading %@ servers" = "Chyba načítání %@ serverů";
/* alert title */
"Error receiving file" = "Chyba při příjmu souboru";
/* No comment provided by engineer. */
"Error removing member" = "Chyba při odebrání člena";
/* No comment provided by engineer. */
"Error saving %@ servers" = "Chyba při ukládání serverů %@";
/* No comment provided by engineer. */
"Error saving group profile" = "Chyba při ukládání profilu skupiny";
@@ -1659,9 +1619,6 @@
/* time unit */
"hours" = "hodin";
/* No comment provided by engineer. */
"How it works" = "Jak to funguje";
/* No comment provided by engineer. */
"How SimpleX works" = "Jak SimpleX funguje";
@@ -1702,7 +1659,7 @@
"Immediately" = "Ihned";
/* No comment provided by engineer. */
"Immune to spam and abuse" = "Odolná vůči spamu a zneužití";
"Immune to spam" = "Odolná vůči spamu a zneužití";
/* No comment provided by engineer. */
"Import" = "Import";
@@ -1771,10 +1728,10 @@
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */
"Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n";
"Instant" = "Okamžitě";
/* No comment provided by engineer. */
"Instantly" = "Okamžitě";
"Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n";
/* No comment provided by engineer. */
"Interface" = "Rozhranní";
@@ -1791,7 +1748,7 @@
/* invalid chat item */
"invalid data" = "neplatné údaje";
/* No comment provided by engineer. */
/* alert title */
"Invalid server address!" = "Neplatná adresa serveru!";
/* item status text */
@@ -1920,9 +1877,6 @@
/* No comment provided by engineer. */
"Live messages" = "Živé zprávy";
/* No comment provided by engineer. */
"Local" = "Místní";
/* No comment provided by engineer. */
"Local name" = "Místní název";
@@ -1935,24 +1889,15 @@
/* No comment provided by engineer. */
"Lock mode" = "Režim zámku";
/* No comment provided by engineer. */
"Make a private connection" = "Vytvořte si soukromé připojení";
/* No comment provided by engineer. */
"Make one message disappear" = "Nechat jednu zprávu zmizet";
/* No comment provided by engineer. */
"Make profile private!" = "Změnit profil na soukromý!";
/* No comment provided by engineer. */
"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Ujistěte se, že adresy %@ serverů jsou ve správném formátu, oddělené řádky a nejsou duplicitní (%@).";
/* No comment provided by engineer. */
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní.";
/* No comment provided by engineer. */
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Mnoho lidí se ptalo: *Pokud SimpleX nemá žádné uživatelské identifikátory, jak může doručovat zprávy?*";
/* No comment provided by engineer. */
"Mark deleted for everyone" = "Označit jako smazané pro všechny";
@@ -2032,7 +1977,7 @@
"Migration is completed" = "Přenesení dokončeno";
/* No comment provided by engineer. */
"Migrations: %@" = "Migrace: %@";
"Migrations:" = "Migrace:";
/* time unit */
"minutes" = "minut";
@@ -2094,9 +2039,6 @@
/* notification */
"New contact:" = "Nový kontakt:";
/* No comment provided by engineer. */
"New database archive" = "Archiv nové databáze";
/* No comment provided by engineer. */
"New desktop app!" = "Nová desktopová aplikace!";
@@ -2157,12 +2099,18 @@
/* No comment provided by engineer. */
"No permission to record voice message" = "Nemáte oprávnění nahrávat hlasové zprávy";
/* No comment provided by engineer. */
"No push server" = "Místní";
/* No comment provided by engineer. */
"No received or sent files" = "Žádné přijaté ani odeslané soubory";
/* copied message info in history */
"no text" = "žádný text";
/* No comment provided by engineer. */
"No user identifiers." = "Bez uživatelských identifikátorů";
/* No comment provided by engineer. */
"Notifications" = "Oznámení";
@@ -2195,9 +2143,6 @@
/* No comment provided by engineer. */
"Old database" = "Stará databáze";
/* No comment provided by engineer. */
"Old database archive" = "Archiv staré databáze";
/* group pref value */
"on" = "zapnuto";
@@ -2214,7 +2159,7 @@
"Onion hosts will not be used." = "Onion hostitelé nebudou použiti.";
/* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**.";
"Only client devices store user profiles, contacts, groups, and messages." = "Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**.";
/* No comment provided by engineer. */
"Only group owners can change group preferences." = "Předvolby skupiny mohou měnit pouze vlastníci skupiny.";
@@ -2267,12 +2212,6 @@
/* No comment provided by engineer. */
"Open Settings" = "Otevřít nastavení";
/* authentication reason */
"Open user profiles" = "Otevřít uživatelské profily";
/* No comment provided by engineer. */
"Open-source protocol and code anybody can run the servers." = "Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli.";
/* member role */
"owner" = "vlastník";
@@ -2301,10 +2240,7 @@
"peer-to-peer" = "peer-to-peer";
/* No comment provided by engineer. */
"People can connect to you only via the links you share." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazů, které sdílíte.";
/* No comment provided by engineer. */
"Periodically" = "Pravidelně";
"Periodic" = "Pravidelně";
/* message decrypt error item */
"Permanent decryption error" = "Chyba dešifrování";
@@ -2360,9 +2296,6 @@
/* No comment provided by engineer. */
"Preserve the last message draft, with attachments." = "Zachování posledního návrhu zprávy s přílohami.";
/* No comment provided by engineer. */
"Preset server" = "Přednastavený server";
/* No comment provided by engineer. */
"Preset server address" = "Přednastavená adresa serveru";
@@ -2387,7 +2320,7 @@
/* No comment provided by engineer. */
"Profile password" = "Heslo profilu";
/* No comment provided by engineer. */
/* alert message */
"Profile update will be sent to your contacts." = "Aktualizace profilu bude zaslána vašim kontaktům.";
/* No comment provided by engineer. */
@@ -2442,7 +2375,7 @@
"Read more" = "Přečíst více";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).";
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
@@ -2450,9 +2383,6 @@
/* No comment provided by engineer. */
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme).";
/* No comment provided by engineer. */
"Read more in our GitHub repository." = "Další informace najdete v našem repozitáři GitHub.";
/* No comment provided by engineer. */
"Receipts are disabled" = "Informace o dodání jsou zakázány";
@@ -2617,9 +2547,6 @@
/* No comment provided by engineer. */
"Save and update group profile" = "Uložit a aktualizovat profil skupiny";
/* No comment provided by engineer. */
"Save archive" = "Uložit archiv";
/* No comment provided by engineer. */
"Save group profile" = "Uložení profilu skupiny";
@@ -2638,7 +2565,7 @@
/* No comment provided by engineer. */
"Save servers" = "Uložit servery";
/* No comment provided by engineer. */
/* alert title */
"Save servers?" = "Uložit servery?";
/* No comment provided by engineer. */
@@ -2725,9 +2652,6 @@
/* No comment provided by engineer. */
"Send notifications" = "Odeslat oznámení";
/* No comment provided by engineer. */
"Send notifications:" = "Odeslat oznámení:";
/* No comment provided by engineer. */
"Send questions and ideas" = "Zasílání otázek a nápadů";
@@ -2821,7 +2745,8 @@
/* No comment provided by engineer. */
"Settings" = "Nastavení";
/* chat item action */
/* alert action
chat item action */
"Share" = "Sdílet";
/* No comment provided by engineer. */
@@ -2830,7 +2755,7 @@
/* No comment provided by engineer. */
"Share address" = "Sdílet adresu";
/* No comment provided by engineer. */
/* alert title */
"Share address with contacts?" = "Sdílet adresu s kontakty?";
/* No comment provided by engineer. */
@@ -2920,9 +2845,6 @@
/* No comment provided by engineer. */
"Stop" = "Zastavit";
/* No comment provided by engineer. */
"Stop chat to enable database actions" = "Zastavte chat pro povolení akcí databáze";
/* 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." = "Zastavení chatu pro export, import nebo smazání databáze chatu. Během zastavení chatu nebudete moci přijímat a odesílat zprávy.";
@@ -2938,10 +2860,10 @@
/* No comment provided by engineer. */
"Stop sending file?" = "Zastavit odesílání souboru?";
/* No comment provided by engineer. */
/* alert action */
"Stop sharing" = "Přestat sdílet";
/* No comment provided by engineer. */
/* alert title */
"Stop sharing address?" = "Přestat sdílet adresu?";
/* authentication reason */
@@ -2998,7 +2920,7 @@
/* No comment provided by engineer. */
"Test servers" = "Testovací servery";
/* No comment provided by engineer. */
/* alert title */
"Tests failed!" = "Testy selhaly!";
/* No comment provided by engineer. */
@@ -3010,9 +2932,6 @@
/* No comment provided by engineer. */
"Thanks to the users contribute via Weblate!" = "Díky uživatelům - přispívejte prostřednictvím Weblate!";
/* No comment provided by engineer. */
"The 1st platform without any user identifiers private by design." = "1. Platforma bez identifikátorů uživatelů - soukromá už od záměru.";
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení.";
@@ -3031,6 +2950,9 @@
/* No comment provided by engineer. */
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!";
/* No comment provided by engineer. */
"The future of messaging" = "Nová generace soukromých zpráv";
/* No comment provided by engineer. */
"The hash of the previous message is different." = "Hash předchozí zprávy se liší.";
@@ -3043,9 +2965,6 @@
/* No comment provided by engineer. */
"The message will be marked as moderated for all members." = "Zpráva bude pro všechny členy označena jako moderovaná.";
/* No comment provided by engineer. */
"The next generation of private messaging" = "Nová generace soukromých zpráv";
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat.";
@@ -3097,15 +3016,15 @@
/* No comment provided by engineer. */
"To make a new connection" = "Vytvoření nového připojení";
/* 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." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů.";
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "K ochraně časového pásma používají obrazové/hlasové soubory UTC.";
/* No comment provided by engineer. */
"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Chcete-li chránit své informace, zapněte zámek SimpleX Lock.\nPřed zapnutím této funkce budete vyzváni k dokončení ověření.";
/* No comment provided by engineer. */
"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů.";
/* No comment provided by engineer. */
"To record voice message please grant permission to use Microphone." = "Chcete-li nahrávat hlasové zprávy, udělte povolení k použití mikrofonu.";
@@ -3331,9 +3250,6 @@
/* No comment provided by engineer. */
"When available" = "Když je k dispozici";
/* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Když někdo požádá o připojení, můžete žádost přijmout nebo odmítnout.";
/* 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." = "Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve.";
@@ -3400,9 +3316,6 @@
/* No comment provided by engineer. */
"You can share this address with your contacts to let them connect with **%@**." = "Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**.";
/* No comment provided by engineer. */
"You can share your address as a link or QR code - anybody can connect to you." = "Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit.";
/* No comment provided by engineer. */
"You can start chat via app Settings / Database or by restarting the app" = "Chat můžete zahájit prostřednictvím aplikace Nastavení / Databáze nebo restartováním aplikace";
@@ -3428,10 +3341,10 @@
"you changed role of %@ to %@" = "změnili jste roli z %1$@ na %2$@";
/* No comment provided by engineer. */
"You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." = "Sami řídíte, přes který server(y) **přijímat** zprávy, své kontakty servery, které používáte k odesílání zpráv.";
"You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu.";
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu.";
"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte.";
/* No comment provided by engineer. */
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení.";
@@ -3499,9 +3412,6 @@
/* 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" = "Pro tuto skupinu používáte inkognito profil - abyste zabránili sdílení svého hlavního profilu, není pozvání kontaktů povoleno";
/* No comment provided by engineer. */
"Your %@ servers" = "Vaše servery %@";
/* No comment provided by engineer. */
"Your calls" = "Vaše hovory";
@@ -3550,9 +3460,6 @@
/* No comment provided by engineer. */
"Your random profile" = "Váš náhodný profil";
/* No comment provided by engineer. */
"Your server" = "Váš server";
/* No comment provided by engineer. */
"Your server address" = "Adresa vašeho serveru";
@@ -3565,6 +3472,3 @@
/* No comment provided by engineer. */
"Your SMP servers" = "Vaše servery SMP";
/* No comment provided by engineer. */
"Your XFTP servers" = "Vaše XFTP servery";
+46 -154
View File
@@ -1,15 +1,6 @@
/* No comment provided by engineer. */
"\n" = "\n";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" " = " ";
/* No comment provided by engineer. */
" (" = " (";
@@ -65,10 +56,7 @@
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */
"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen oder eine Verbindung über einen Link herzustellen, den Sie erhalten haben.";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Neuen Kontakt hinzufügen**: Um einen Einmal-QR-Code oder -Link für Ihren Kontakt zu erzeugen.";
"**Create 1-time link**: to create and share a new invitation link." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen.";
/* No comment provided by engineer. */
"**Create group**: to create a new group." = "**Gruppe erstellen**: Um eine neue Gruppe zu erstellen.";
@@ -80,10 +68,10 @@
"**e2e encrypted** video call" = "**E2E-verschlüsselter** Videoanruf";
/* No comment provided by engineer. */
"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen.";
"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen.";
/* No comment provided by engineer. */
"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).";
"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen).";
/* No comment provided by engineer. */
"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen.";
@@ -92,7 +80,7 @@
"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren.";
/* No comment provided by engineer. */
"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde.";
"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde.";
/* No comment provided by engineer. */
"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist.";
@@ -340,12 +328,6 @@
/* No comment provided by engineer. */
"Abort changing address?" = "Wechsel der Empfängeradresse beenden?";
/* No comment provided by engineer. */
"About SimpleX" = "Über SimpleX";
/* No comment provided by engineer. */
"About SimpleX address" = "Über die SimpleX-Adresse";
/* No comment provided by engineer. */
"About SimpleX Chat" = "Über SimpleX Chat";
@@ -385,12 +367,6 @@
/* No comment provided by engineer. */
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet.";
/* No comment provided by engineer. */
"Add contact" = "Kontakt hinzufügen";
/* No comment provided by engineer. */
"Add preset servers" = "Füge voreingestellte Server hinzu";
/* No comment provided by engineer. */
"Add profile" = "Profil hinzufügen";
@@ -577,6 +553,9 @@
/* No comment provided by engineer. */
"Answer call" = "Anruf annehmen";
/* No comment provided by engineer. */
"Anybody can host servers." = "Jeder kann seine eigenen Server aufsetzen.";
/* No comment provided by engineer. */
"App build: %@" = "App Build: %@";
@@ -820,7 +799,8 @@
/* No comment provided by engineer. */
"Can't message member" = "Mitglied kann nicht benachrichtigt werden";
/* alert button */
/* alert action
alert button */
"Cancel" = "Abbrechen";
/* No comment provided by engineer. */
@@ -890,9 +870,6 @@
/* chat item text */
"changing address…" = "Wechsel der Empfängeradresse wurde gestartet…";
/* No comment provided by engineer. */
"Chat archive" = "Datenbank Archiv";
/* No comment provided by engineer. */
"Chat colors" = "Chat-Farben";
@@ -941,7 +918,7 @@
/* No comment provided by engineer. */
"Chats" = "Chats";
/* No comment provided by engineer. */
/* alert title */
"Check server address and try again." = "Überprüfen Sie die Serveradresse und versuchen Sie es nochmal.";
/* No comment provided by engineer. */
@@ -1004,9 +981,6 @@
/* No comment provided by engineer. */
"Configure ICE servers" = "ICE-Server konfigurieren";
/* No comment provided by engineer. */
"Configured %@ servers" = "Konfigurierte %@ Server";
/* No comment provided by engineer. */
"Confirm" = "Bestätigen";
@@ -1235,9 +1209,6 @@
/* No comment provided by engineer. */
"Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil.";
/* No comment provided by engineer. */
"Create an address to let people connect with you." = "Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können.";
/* server test step */
"Create file" = "Datei erstellen";
@@ -1277,9 +1248,6 @@
/* copied message info */
"Created at: %@" = "Erstellt um: %@";
/* No comment provided by engineer. */
"Created on %@" = "Erstellt am %@";
/* No comment provided by engineer. */
"Creating archive link" = "Archiv-Link erzeugen";
@@ -1400,7 +1368,8 @@
/* No comment provided by engineer. */
"default (yes)" = "Voreinstellung (Ja)";
/* chat item action
/* alert action
chat item action
swipe action */
"Delete" = "Löschen";
@@ -1425,12 +1394,6 @@
/* No comment provided by engineer. */
"Delete and notify contact" = "Kontakt löschen und benachrichtigen";
/* No comment provided by engineer. */
"Delete archive" = "Archiv löschen";
/* No comment provided by engineer. */
"Delete chat archive?" = "Chat Archiv löschen?";
/* No comment provided by engineer. */
"Delete chat profile" = "Chat-Profil löschen";
@@ -1909,9 +1872,6 @@
/* No comment provided by engineer. */
"Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage";
/* No comment provided by engineer. */
"Error accessing database file" = "Fehler beim Zugriff auf die Datenbankdatei";
/* No comment provided by engineer. */
"Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern";
@@ -1999,9 +1959,6 @@
/* No comment provided by engineer. */
"Error joining group" = "Fehler beim Beitritt zur Gruppe";
/* No comment provided by engineer. */
"Error loading %@ servers" = "Fehler beim Laden von %@ Servern";
/* No comment provided by engineer. */
"Error migrating settings" = "Fehler beim Migrieren der Einstellungen";
@@ -2023,9 +1980,6 @@
/* No comment provided by engineer. */
"Error resetting statistics" = "Fehler beim Zurücksetzen der Statistiken";
/* No comment provided by engineer. */
"Error saving %@ servers" = "Fehler beim Speichern der %@-Server";
/* No comment provided by engineer. */
"Error saving group profile" = "Fehler beim Speichern des Gruppenprofils";
@@ -2428,9 +2382,6 @@
/* time unit */
"hours" = "Stunden";
/* No comment provided by engineer. */
"How it works" = "Wie es funktioniert";
/* No comment provided by engineer. */
"How SimpleX works" = "Wie SimpleX funktioniert";
@@ -2474,7 +2425,7 @@
"Immediately" = "Sofort";
/* No comment provided by engineer. */
"Immune to spam and abuse" = "Immun gegen Spam und Missbrauch";
"Immune to spam" = "Immun gegen Spam und Missbrauch";
/* No comment provided by engineer. */
"Import" = "Importieren";
@@ -2573,10 +2524,10 @@
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat)";
/* No comment provided by engineer. */
"Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n";
"Instant" = "Sofort";
/* No comment provided by engineer. */
"Instantly" = "Sofort";
"Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n";
/* No comment provided by engineer. */
"Interface" = "Schnittstelle";
@@ -2614,7 +2565,7 @@
/* No comment provided by engineer. */
"Invalid response" = "Ungültige Reaktion";
/* No comment provided by engineer. */
/* alert title */
"Invalid server address!" = "Ungültige Serveradresse!";
/* item status text */
@@ -2719,7 +2670,7 @@
/* No comment provided by engineer. */
"Joining group" = "Der Gruppe beitreten";
/* No comment provided by engineer. */
/* alert action */
"Keep" = "Behalten";
/* No comment provided by engineer. */
@@ -2728,7 +2679,7 @@
/* No comment provided by engineer. */
"Keep the app open to use it from desktop" = "Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können";
/* No comment provided by engineer. */
/* alert title */
"Keep unused invitation?" = "Nicht genutzte Einladung behalten?";
/* No comment provided by engineer. */
@@ -2785,9 +2736,6 @@
/* No comment provided by engineer. */
"Live messages" = "Live Nachrichten";
/* No comment provided by engineer. */
"Local" = "Lokal";
/* No comment provided by engineer. */
"Local name" = "Lokaler Name";
@@ -2800,24 +2748,15 @@
/* No comment provided by engineer. */
"Lock mode" = "Sperr-Modus";
/* No comment provided by engineer. */
"Make a private connection" = "Stellen Sie eine private Verbindung her";
/* No comment provided by engineer. */
"Make one message disappear" = "Eine verschwindende Nachricht verfassen";
/* No comment provided by engineer. */
"Make profile private!" = "Privates Profil erzeugen!";
/* No comment provided by engineer. */
"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Stellen Sie sicher, dass die %@-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@).";
/* No comment provided by engineer. */
"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind.";
/* No comment provided by engineer. */
"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?*";
/* No comment provided by engineer. */
"Mark deleted for everyone" = "Für Alle als gelöscht markieren";
@@ -2987,7 +2926,7 @@
"Migration is completed" = "Die Migration wurde abgeschlossen";
/* No comment provided by engineer. */
"Migrations: %@" = "Migrationen: %@";
"Migrations:" = "Migrationen:";
/* time unit */
"minutes" = "Minuten";
@@ -3070,9 +3009,6 @@
/* notification */
"New contact:" = "Neuer Kontakt:";
/* No comment provided by engineer. */
"New database archive" = "Neues Datenbankarchiv";
/* No comment provided by engineer. */
"New desktop app!" = "Neue Desktop-App!";
@@ -3157,12 +3093,18 @@
/* No comment provided by engineer. */
"No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten";
/* No comment provided by engineer. */
"No push server" = "Lokal";
/* No comment provided by engineer. */
"No received or sent files" = "Keine empfangenen oder gesendeten Dateien";
/* copied message info in history */
"no text" = "Kein Text";
/* No comment provided by engineer. */
"No user identifiers." = "Keine Benutzerkennungen.";
/* No comment provided by engineer. */
"Not compatible!" = "Nicht kompatibel!";
@@ -3207,9 +3149,6 @@
/* No comment provided by engineer. */
"Old database" = "Alte Datenbank";
/* No comment provided by engineer. */
"Old database archive" = "Altes Datenbankarchiv";
/* group pref value */
"on" = "Ein";
@@ -3226,7 +3165,7 @@
"Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet.";
/* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden.";
"Only client devices store user profiles, contacts, groups, and messages." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden.";
/* No comment provided by engineer. */
"Only delete conversation" = "Nur die Chat-Inhalte löschen";
@@ -3285,18 +3224,9 @@
/* authentication reason */
"Open migration to another device" = "Migration auf ein anderes Gerät öffnen";
/* No comment provided by engineer. */
"Open server settings" = "Server-Einstellungen öffnen";
/* No comment provided by engineer. */
"Open Settings" = "Geräte-Einstellungen öffnen";
/* authentication reason */
"Open user profiles" = "Benutzerprofile öffnen";
/* No comment provided by engineer. */
"Open-source protocol and code anybody can run the servers." = "Open-Source-Protokoll und -Code Jede Person kann ihre eigenen Server aufsetzen und nutzen.";
/* No comment provided by engineer. */
"Opening app…" = "App wird geöffnet…";
@@ -3318,9 +3248,6 @@
/* No comment provided by engineer. */
"Other" = "Andere";
/* No comment provided by engineer. */
"Other %@ servers" = "Andere %@ Server";
/* No comment provided by engineer. */
"other errors" = "Andere Fehler";
@@ -3376,10 +3303,7 @@
"Pending" = "Ausstehend";
/* No comment provided by engineer. */
"People can connect to you only via the links you share." = "Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen.";
/* No comment provided by engineer. */
"Periodically" = "Periodisch";
"Periodic" = "Periodisch";
/* message decrypt error item */
"Permanent decryption error" = "Entschlüsselungsfehler";
@@ -3456,9 +3380,6 @@
/* No comment provided by engineer. */
"Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren.";
/* No comment provided by engineer. */
"Preset server" = "Voreingestellter Server";
/* No comment provided by engineer. */
"Preset server address" = "Voreingestellte Serveradresse";
@@ -3507,7 +3428,7 @@
/* No comment provided by engineer. */
"Profile theme" = "Profil-Design";
/* No comment provided by engineer. */
/* alert message */
"Profile update will be sent to your contacts." = "Profil-Aktualisierung wird an Ihre Kontakte gesendet.";
/* No comment provided by engineer. */
@@ -3592,10 +3513,10 @@
"Read more" = "Mehr erfahren";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen.";
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen.";
/* No comment provided by engineer. */
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen.";
@@ -3603,9 +3524,6 @@
/* No comment provided by engineer. */
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Erfahren Sie in unserem [GitHub-Repository](https://github.com/simplex-chat/simplex-chat#readme) mehr dazu.";
/* No comment provided by engineer. */
"Read more in our GitHub repository." = "Erfahren Sie in unserem GitHub-Repository mehr dazu.";
/* No comment provided by engineer. */
"Receipts are disabled" = "Bestätigungen sind deaktiviert";
@@ -3857,9 +3775,6 @@
/* No comment provided by engineer. */
"Save and update group profile" = "Gruppen-Profil sichern und aktualisieren";
/* No comment provided by engineer. */
"Save archive" = "Archiv speichern";
/* No comment provided by engineer. */
"Save group profile" = "Gruppenprofil speichern";
@@ -3878,7 +3793,7 @@
/* No comment provided by engineer. */
"Save servers" = "Alle Server speichern";
/* No comment provided by engineer. */
/* alert title */
"Save servers?" = "Alle Server speichern?";
/* No comment provided by engineer. */
@@ -4031,9 +3946,6 @@
/* No comment provided by engineer. */
"Send notifications" = "Benachrichtigungen senden";
/* No comment provided by engineer. */
"Send notifications:" = "Benachrichtigungen senden:";
/* No comment provided by engineer. */
"Send questions and ideas" = "Senden Sie Fragen und Ideen";
@@ -4196,7 +4108,8 @@
/* No comment provided by engineer. */
"Shape profile images" = "Form der Profil-Bilder";
/* chat item action */
/* alert action
chat item action */
"Share" = "Teilen";
/* No comment provided by engineer. */
@@ -4205,7 +4118,7 @@
/* No comment provided by engineer. */
"Share address" = "Adresse teilen";
/* No comment provided by engineer. */
/* alert title */
"Share address with contacts?" = "Die Adresse mit Kontakten teilen?";
/* No comment provided by engineer. */
@@ -4370,9 +4283,6 @@
/* No comment provided by engineer. */
"Stop chat" = "Chat beenden";
/* No comment provided by engineer. */
"Stop chat to enable database actions" = "Chat beenden, um Datenbankaktionen zu erlauben";
/* 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." = "Beenden Sie den Chat, um die Chat-Datenbank zu exportieren, zu importieren oder zu löschen. Solange der Chat angehalten ist, können Sie keine Nachrichten empfangen oder senden.";
@@ -4388,10 +4298,10 @@
/* No comment provided by engineer. */
"Stop sending file?" = "Das Senden der Datei beenden?";
/* No comment provided by engineer. */
/* alert action */
"Stop sharing" = "Teilen beenden";
/* No comment provided by engineer. */
/* alert title */
"Stop sharing address?" = "Das Teilen der Adresse beenden?";
/* authentication reason */
@@ -4487,7 +4397,7 @@
/* No comment provided by engineer. */
"Test servers" = "Teste alle Server";
/* No comment provided by engineer. */
/* alert title */
"Tests failed!" = "Tests sind fehlgeschlagen!";
/* No comment provided by engineer. */
@@ -4499,9 +4409,6 @@
/* No comment provided by engineer. */
"Thanks to the users contribute via Weblate!" = "Dank der Nutzer - Tragen Sie per Weblate bei!";
/* No comment provided by engineer. */
"The 1st platform without any user identifiers private by design." = "Die erste Plattform ohne Benutzerkennungen Privat per Design.";
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen.";
@@ -4526,6 +4433,9 @@
/* No comment provided by engineer. */
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen!";
/* No comment provided by engineer. */
"The future of messaging" = "Die nächste Generation von privatem Messaging";
/* No comment provided by engineer. */
"The hash of the previous message is different." = "Der Hash der vorherigen Nachricht unterscheidet sich.";
@@ -4544,9 +4454,6 @@
/* No comment provided by engineer. */
"The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden.";
/* No comment provided by engineer. */
"The next generation of private messaging" = "Die nächste Generation von privatem Messaging";
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden.";
@@ -4634,9 +4541,6 @@
/* No comment provided by engineer. */
"To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen";
/* 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." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind.";
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen.";
@@ -4646,6 +4550,9 @@
/* No comment provided by engineer. */
"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt.";
/* No comment provided by engineer. */
"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind.";
/* No comment provided by engineer. */
"To record speech please grant permission to use Microphone." = "Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen.";
@@ -5033,9 +4940,6 @@
/* No comment provided by engineer. */
"when IP hidden" = "Wenn die IP-Adresse versteckt ist";
/* No comment provided by engineer. */
"When people request to connect, you can accept or reject it." = "Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen.";
/* 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." = "Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden.";
@@ -5177,9 +5081,6 @@
/* No comment provided by engineer. */
"You can share this address with your contacts to let them connect with **%@**." = "Sie können diese Adresse mit Ihren Kontakten teilen, um sie mit **%@** verbinden zu lassen.";
/* No comment provided by engineer. */
"You can share your address as a link or QR code - anybody can connect to you." = "Sie können Ihre Adresse als Link oder als QR-Code teilen Jede Person kann sich darüber mit Ihnen verbinden.";
/* No comment provided by engineer. */
"You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten";
@@ -5192,7 +5093,7 @@
/* No comment provided by engineer. */
"You can use markdown to format messages:" = "Um Nachrichteninhalte zu formatieren, können Sie Markdowns verwenden:";
/* No comment provided by engineer. */
/* alert message */
"You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen.";
/* No comment provided by engineer. */
@@ -5211,10 +5112,10 @@
"you changed role of %@ to %@" = "Sie haben die Rolle von %1$@ auf %2$@ geändert";
/* No comment provided by engineer. */
"You control through which server(s) **to receive** the messages, your contacts the servers you use to message them." = "Sie können selbst festlegen, über welche Server Sie Ihre Nachrichten **empfangen** und an Ihre Kontakte **senden** wollen.";
"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut.";
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut.";
"You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann.";
/* No comment provided by engineer. */
"You have already requested connection via this address!" = "Sie haben über diese Adresse bereits eine Verbindung beantragt!";
@@ -5306,9 +5207,6 @@
/* 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" = "Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt";
/* No comment provided by engineer. */
"Your %@ servers" = "Ihre %@-Server";
/* No comment provided by engineer. */
"Your calls" = "Anrufe";
@@ -5372,9 +5270,6 @@
/* No comment provided by engineer. */
"Your random profile" = "Ihr Zufallsprofil";
/* No comment provided by engineer. */
"Your server" = "Ihr Server";
/* No comment provided by engineer. */
"Your server address" = "Ihre Serveradresse";
@@ -5387,6 +5282,3 @@
/* No comment provided by engineer. */
"Your SMP servers" = "Ihre SMP-Server";
/* No comment provided by engineer. */
"Your XFTP servers" = "Ihre XFTP-Server";
-4
View File
@@ -1,9 +1,6 @@
/* No comment provided by engineer. */
"_italic_" = "\\_italic_";
/* No comment provided by engineer. */
"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact.";
/* No comment provided by engineer. */
"*bold*" = "\\*bold*";
@@ -27,4 +24,3 @@
/* No comment provided by engineer. */
"No group!" = "Group not found!";

Some files were not shown because too many files have changed in this diff Show More