diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3ef9fa088..b89f6ccce0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,17 +10,25 @@ on: - "!*-fdroid" - "!*-armv7a" pull_request: - paths-ignore: - - "apps/ios" - - "apps/multiplatform" - - "blog" - - "docs" - - "fastlane" - - "images" - - "packages" - - "website" - - "README.md" - - "PRIVACY.md" + paths: + - "src/**" + - "apps/simplex-chat/**" + - "apps/simplex-bot/**" + - "apps/simplex-bot-advanced/**" + - "apps/simplex-broadcast-bot/**" + - "apps/simplex-directory-service/**" + - "tests/**" + - "bots/src/**" + - "simplex-chat.cabal" + - "cabal.project" + - "Dockerfile*" + - "scripts/ci/**" + - "scripts/desktop/**" + - ".github/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') }} # This workflow uses custom actions (prepare-build and prepare-release) defined in: # @@ -294,6 +302,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | + export ASSETS_DIR='../../assets' scripts/desktop/make-deb-linux.sh - name: Prepare Desktop @@ -319,6 +328,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | + export ASSETS_DIR='../../assets' scripts/desktop/make-appimage-linux.sh - name: Prepare AppImage @@ -369,6 +379,100 @@ jobs: exit 1 fi +# ================================= +# Linux PostgreSQL Library Build +# ================================= + + build-linux-postgres: + name: "ubuntu-22.04-x86_64 (Postgres lib), GHC: ${{ needs.variables.outputs.GHC_VER }}" + needs: [maybe-release, variables] + runs-on: ubuntu-22.04 + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Get UID and GID + id: ids + run: | + echo "uid=$(id -u)" >> $GITHUB_OUTPUT + echo "gid=$(id -g)" >> $GITHUB_OUTPUT + + - name: Free disk space + shell: bash + run: ./scripts/ci/linux_util_free_space.sh + + - name: Restore cached build + uses: actions/cache@v4 + with: + path: | + ~/.cabal/store + dist-newstyle + key: ubuntu-22.04-x86_64-postgres-ghc${{ needs.variables.outputs.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} + + - name: Set up Docker Buildx + uses: simplex-chat/docker-setup-buildx-action@v3 + + - name: Build and cache Docker image + uses: simplex-chat/docker-build-push-action@v6 + with: + context: . + load: true + file: Dockerfile.build + tags: build/22.04:latest + build-args: | + TAG=22.04 + HASH=sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5 + GHC=${{ needs.variables.outputs.GHC_VER }} + USER_UID=${{ steps.ids.outputs.uid }} + USER_GID=${{ steps.ids.outputs.gid }} + + - name: Start container + shell: bash + run: | + docker run -t -d \ + --name builder \ + -v ~/.cabal:/root/.cabal \ + -v /home/runner/work/_temp:/home/runner/work/_temp \ + -v ${{ github.workspace }}:/project \ + build/22.04:latest + + - name: Prepare cabal.project.local + shell: bash + run: | + echo "ignore-project: False" >> cabal.project.local + echo "package direct-sqlcipher" >> cabal.project.local + echo " flags: +openssl" >> cabal.project.local + + - name: Build postgres library + shell: docker exec -t builder sh -eu {0} + run: | + cabal clean + cabal update + scripts/desktop/build-lib-linux.sh postgres + + - name: Copy libs from container + shell: bash + run: | + ARCH=x86_64 + GHC_VER=${{ needs.variables.outputs.GHC_VER }} + BUILD_DIR=$(echo dist-newstyle/build/${ARCH}-linux/ghc-${GHC_VER}/simplex-chat-*) + mkdir -p postgres-libs + cp ${BUILD_DIR}/build/libsimplex.so postgres-libs/ + cp ${BUILD_DIR}/build/deps/* postgres-libs/ + + - name: Upload postgres libs artifact + uses: actions/upload-artifact@v4 + with: + name: simplex-libs-linux-postgres-x86_64 + path: postgres-libs/ + + - name: Fix permissions for cache + shell: bash + run: | + sudo chmod -R 777 dist-newstyle ~/.cabal + sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal + # ========================= # MacOS Build # ========================= @@ -447,6 +551,7 @@ jobs: APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }} APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }} run: | + export ASSETS_DIR='../../assets' scripts/ci/build-desktop-mac.sh path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg) echo "package_path=$path" >> $GITHUB_OUTPUT @@ -573,7 +678,7 @@ jobs: export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/build-lib-windows.sh cd apps/multiplatform - ./gradlew packageMsi + ./gradlew -Psimplex.assets.dir=../../assets packageMsi rm -rf dist-newstyle/src/direct-sq* path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g') echo "package_path=$path" >> $GITHUB_OUTPUT @@ -605,7 +710,7 @@ jobs: release-nodejs-libs: runs-on: ubuntu-latest - needs: [build-linux, build-macos] + needs: [build-linux, build-linux-postgres, build-macos] if: startsWith(github.ref, 'refs/tags/v') && (!cancelled()) steps: - name: Checkout current repository @@ -614,6 +719,13 @@ jobs: - name: Install packages for archiving run: sudo apt install -y msitools gcc-mingw-w64 + - name: Download postgres libs artifact + if: needs.build-linux-postgres.result == 'success' + uses: actions/download-artifact@v4 + with: + name: simplex-libs-linux-postgres-x86_64 + path: ${{ runner.temp }}/postgres-libs + - name: Build archives run: | INIT_DIR='${{ runner.temp }}/artifacts' @@ -670,6 +782,17 @@ jobs: zip -r "${PREFIX}-windows-x86_64.zip" libs mv "${PREFIX}-windows-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR" + # Linux PostgreSQL (only if postgres build succeeded) + # ------------------------------------------------- + POSTGRES_LIBS='${{ runner.temp }}/postgres-libs' + if [ -d "$POSTGRES_LIBS" ]; then + mkdir -p linux-postgres/libs + cp "${POSTGRES_LIBS}"/*.so linux-postgres/libs/ + cd linux-postgres + zip -r "${PREFIX}-linux-x86_64-postgres.zip" libs + mv "${PREFIX}-linux-x86_64-postgres.zip" "$RELEASE_DIR" && cd "$INIT_DIR" + fi + - name: Create release in libs repo and upload artifacts uses: softprops/action-gh-release@v2 with: diff --git a/README.md b/README.md index 818ed7142f..252fc95708 100644 --- a/README.md +++ b/README.md @@ -425,9 +425,9 @@ Please do NOT report security vulnerabilities via GitHub issues. ## License -This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, and associated branding materials are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) file. +This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, associated branding materials, and application and website graphic assets (illustrations, images, visual designs, etc.) are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) and [ASSETS_LICENSE](./assets/ASSETS_LICENSE.md) files respectively. -Graphic designs, artworks and layouts are not licensed for re-use. If you want to use them in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source. +If you want to use any graphic assets in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source. [iOS app](https://apps.apple.com/us/app/simplex-chat/id1605771084)   diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 20653ab9db..85bb8a30b4 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2183,7 +2183,7 @@ func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws withAnimation { let savedOnboardingStage = onboardingStageDefault.get() m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 - ? .step3_ChooseServerOperators + ? .step4_NetworkCommitments : savedOnboardingStage if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { m.setDeliveryReceipts = true diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index d0ff1934ba..1839651daa 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -172,8 +172,8 @@ struct ChatItemContentView: View { case .rcvBlocked: deletedItemView() case let .sndDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) case let .rcvDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) - case .sndGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) - case .rcvGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) + case let .sndGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo)) + case let .rcvGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo)) case .chatBanner: EmptyView() case let .invalidJSON(json): CIInvalidJSONView(json: json) } @@ -257,6 +257,12 @@ struct ChatItemContentView: View { e2eeInfoText("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.") } + private func groupE2EEInfoText(_ info: E2EEInfo) -> Text { + info.public == true + ? e2eeInfoText("Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages.") + : e2eeInfoNoPQText() + } + private func e2eeInfoText(_ s: LocalizedStringKey) -> Text { Text(s) .font(.caption) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index fd47ddfacb..334abd76ee 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -515,7 +515,7 @@ struct ComposeView: View { sendMessageView( disableSendButton, placeholder: chat.chatInfo.groupInfo.map { gi in - gi.useRelays && gi.membership.memberRole >= .owner + gi.useRelays && gi.membership.memberRole >= .owner && chat.chatInfo.groupChatScope() == nil ? NSLocalizedString("Broadcast", comment: "compose placeholder for channel owner") : nil } ?? nil @@ -1659,7 +1659,7 @@ struct ComposeView: View { type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, scope: chat.chatInfo.groupChatScope(), - sendAsGroup: chat.chatInfo.groupInfo.map { $0.useRelays && $0.membership.memberRole >= .owner } ?? false, + sendAsGroup: chat.chatInfo.sendAsGroup, live: live, ttl: ttl, composedMessages: msgs diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 9279c53c83..21685fccd1 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -103,6 +103,10 @@ struct GroupChatInfoView: View { } } + let showUserSupportChat = groupInfo.membership.memberActive + && ((groupInfo.fullGroupPreferences.support.on && groupInfo.membership.memberRole < .moderator) + || groupInfo.membership.supportChat != nil) + if groupInfo.useRelays { Section { // TODO [relays] allow other owners to manage channel link (requires protocol changes to share link ownership) @@ -124,6 +128,12 @@ struct GroupChatInfoView: View { if groupInfo.isOwner || members.contains(where: { $0.wrapped.memberRole >= .owner }) { channelMembersButton() } + if groupInfo.membership.memberRole >= .moderator { + memberSupportButton() + } + if showUserSupportChat { + UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) + } } footer: { if !groupInfo.isOwner && groupInfo.groupProfile.publicGroup?.groupLink != nil { Text("You can share a link or a QR code - anybody will be able to join the channel.") @@ -141,8 +151,7 @@ struct GroupChatInfoView: View { if groupInfo.canModerate { GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) } - if groupInfo.membership.memberActive - && (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) { + if showUserSupportChat { UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) } } header: { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index af7054db01..4dff86f7bb 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -121,13 +121,15 @@ struct GroupMemberInfoView: View { } if connectionLoaded { + let showMemberSupportChat = !openedFromSupportChat + && groupInfo.membership.memberRole >= .moderator + && member.memberRole != .relay + && ((groupInfo.fullGroupPreferences.support.on && member.memberRole < .moderator) + || member.supportChat != nil) if member.memberActive { Section { - if !openedFromSupportChat - && groupInfo.membership.memberRole >= .moderator - && member.memberRole != .relay - && (member.memberRole < .moderator || member.supportChat != nil) { + if showMemberSupportChat { MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId) } if let code = connectionCode, @@ -142,6 +144,10 @@ struct GroupMemberInfoView: View { // synchronizeConnectionButtonForce() // } } + } else if groupInfo.useRelays && member.memberCurrent && showMemberSupportChat { + Section { + MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId) + } } if let contactLink = member.contactLink { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 84e852f5a3..cc2feef706 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -46,13 +46,30 @@ struct GroupPreferencesView: View { featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) featureSection(.files, $preferences.files.enable, $preferences.files.role) featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) - featureSection(.reports, $preferences.reports.enable) + featureSection(.reports, $preferences.reports.enable, disabled: true) // enable reports in 7.0 once directory support added featureSection(.history, $preferences.history.enable) + featureSection(.support, $preferences.support.enable, disabled: true) } else { featureSection(.timedMessages, $preferences.timedMessages.enable) featureSection(.fullDelete, $preferences.fullDelete.enable) featureSection(.reactions, $preferences.reactions.enable) featureSection(.history, $preferences.history.enable) + let supportNotice = NSLocalizedString("Chats with admins in public channels have no E2E encryption - use only with trusted chat relays.", comment: "alert message") + featureSection(.support, $preferences.support.enable, notice: supportNotice) + .onChange(of: preferences.support.enable) { enable in + if enable == .on { + showAlert( + NSLocalizedString("Enable chats with admins?", comment: "alert title"), + message: supportNotice, + actions: {[ + UIAlertAction(title: NSLocalizedString("Enable", comment: "alert button"), style: .destructive) { _ in }, + UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) { _ in + preferences.support.enable = .off + } + ]} + ) + } + } } if groupInfo.isOwner { @@ -92,7 +109,7 @@ struct GroupPreferencesView: View { } } - private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding, _ enableForRole: Binding? = nil) -> some View { + private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding, _ enableForRole: Binding? = nil, disabled: Bool = false, notice: String? = nil) -> some View { Section { let color: Color = enableFeature.wrappedValue == .on ? .green : theme.colors.secondary let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon @@ -103,9 +120,9 @@ struct GroupPreferencesView: View { set: { on, _ in enableFeature.wrappedValue = on ? .on : .off } ) settingsRow(icon, color: color) { - Toggle(feature.text, isOn: enable) + Toggle(feature.text(isChannel: groupInfo.isChannel), isOn: enable) } - .disabled(feature == .reports) // remove in 6.4 + .disabled(disabled) if timedOn { DropdownCustomTimePicker( selection: $preferences.timedMessages.ttl, @@ -126,7 +143,7 @@ struct GroupPreferencesView: View { } } else { settingsRow(icon, color: color) { - infoRow(Text(feature.text), enableFeature.wrappedValue.text) + infoRow(Text(feature.text(isChannel: groupInfo.isChannel)), enableFeature.wrappedValue.text) } if timedOn { infoRow("Delete after", timeText(preferences.timedMessages.ttl)) @@ -144,8 +161,11 @@ struct GroupPreferencesView: View { } } } footer: { - Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner)) - .foregroundColor(theme.colors.secondary) + VStack(alignment: .leading) { + Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner, isChannel: groupInfo.isChannel)) + if let notice { Text(notice) } + } + .foregroundColor(theme.colors.secondary) } .onChange(of: enableFeature.wrappedValue) { enabled in if case .off = enabled { diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift index 3dc27c08f6..880933985c 100644 --- a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift +++ b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift @@ -45,7 +45,7 @@ struct MemberSupportView: View { : membersWithChats.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } if membersWithChats.isEmpty { - Text("No chats with members") + Text(groupInfo.fullGroupPreferences.support.on ? "No chats with members" : "Chats with members are disabled") .foregroundColor(.secondary) } else { List { diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 967fedf293..dc4971aafa 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -401,6 +401,13 @@ struct ChatListView: View { .padding(.top, oneHandUI ? 8 : 0) .id("searchBar") } + if !oneHandUICardShown { + OneHandUICard() + .padding(.vertical, 6) + .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } if #available(iOS 16.0, *) { ForEach(cs, id: \.viewId) { chat in ChatListNavLink(chat: chat, parentSheet: $sheet) @@ -420,13 +427,6 @@ struct ChatListView: View { .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) } } - if !oneHandUICardShown { - OneHandUICard() - .padding(.vertical, 6) - .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } if !addressCreationCardShown && hasConversations { ConnectBannerCard() .padding(.vertical, 6) @@ -839,11 +839,11 @@ struct TagsView: View { nil } let active = tag == selectedPresetTag - let (icon, text) = presetTagLabel(tag: tag, active: active) + let (icon, menuIcon, text) = presetTagLabel(tag: tag, active: active) let color: Color = active ? .accentColor : .secondary HStack(spacing: 4) { - Image(systemName: icon) + Image(systemName: menuIcon ?? icon) .foregroundColor(color) ZStack { Text(text).fontWeight(.semibold).foregroundColor(.clear) @@ -886,9 +886,9 @@ struct TagsView: View { Button { setActiveFilter(filter: .presetTag(tag)) } label: { - let (systemName, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag) + let (icon, _, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag) HStack { - Image(systemName: systemName) + Image(systemName: icon) Text(text) } } @@ -896,8 +896,8 @@ struct TagsView: View { } } label: { if let tag = selectedPresetTag, tag.сollapse { - let (systemName, _) = presetTagLabel(tag: tag, active: true) - Image(systemName: systemName) + let (icon, menuIcon, _) = presetTagLabel(tag: tag, active: true) + Image(systemName: menuIcon ?? icon) .foregroundColor(.accentColor) } else { Image(systemName: "list.bullet") @@ -907,15 +907,15 @@ struct TagsView: View { .frame(minWidth: 28) } - private func presetTagLabel(tag: PresetTag, active: Bool) -> (String, LocalizedStringKey) { + private func presetTagLabel(tag: PresetTag, active: Bool) -> (item: String, menu: String?, label: LocalizedStringKey) { switch tag { - case .groupReports: (active ? "flag.fill" : "flag", "Reports") - case .favorites: (active ? "star.fill" : "star", "Favorites") - case .contacts: (active ? "person.fill" : "person", "Contacts") - case .groups: (active ? "person.2.fill" : "person.2", "Groups") - case .channels: (active ? "antenna.radiowaves.left.and.right.circle.fill" : "antenna.radiowaves.left.and.right.circle", "Channels") - case .business: (active ? "briefcase.fill" : "briefcase", "Businesses") - case .notes: (active ? "folder.fill" : "folder", "Notes") + case .groupReports: (item: active ? "flag.fill" : "flag", menu: nil, label: "Reports") + case .favorites: (item: active ? "star.fill" : "star", menu: nil, label: "Favorites") + case .contacts: (item: active ? "person.fill" : "person", menu: nil, label: "Contacts") + case .groups: (item: active ? "person.2.fill" : "person.2", menu: nil, label: "Groups") + case .channels: (item: active ? "antenna.radiowaves.left.and.right.circle.fill" : "antenna.radiowaves.left.and.right", menu: "antenna.radiowaves.left.and.right", label: "Channels") + case .business: (item: active ? "briefcase.fill" : "briefcase", menu: nil, label: "Businesses") + case .notes: (item: active ? "folder.fill" : "folder", menu: nil, label: "Notes") } } diff --git a/apps/ios/Shared/Views/ChatList/OneHandUICard.swift b/apps/ios/Shared/Views/ChatList/OneHandUICard.swift index 059f24cc82..132a19d7e7 100644 --- a/apps/ios/Shared/Views/ChatList/OneHandUICard.swift +++ b/apps/ios/Shared/Views/ChatList/OneHandUICard.swift @@ -11,27 +11,46 @@ import SimpleXChat struct OneHandUICard: View { @EnvironmentObject var theme: AppTheme - @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false + @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @State private var showOneHandUIAlert = false var body: some View { - ZStack(alignment: .topTrailing) { - VStack(alignment: .leading, spacing: 8) { - Text("Toggle chat list:").font(.title3) - Toggle("Reachable chat toolbar", isOn: $oneHandUI) + HStack(spacing: 2) { + segment( + icon: "platter.filled.bottom.and.arrow.down.iphone", + text: "Bottom bar", + isSelected: oneHandUI + ) { + withAnimation { oneHandUI = true } } - Image(systemName: "multiply") - .foregroundColor(theme.colors.secondary) - .onTapGesture { - showOneHandUIAlert = true + .background { if oneHandUI { Color(uiColor: .systemGray5) } } + .background(ToolbarMaterial.material(toolbarMaterial)) + ZStack(alignment: .trailing) { + segment( + icon: "platter.filled.top.and.arrow.up.iphone", + text: "Top bar", + isSelected: !oneHandUI + ) { + withAnimation { oneHandUI = false } } + Image(systemName: "multiply") + .foregroundColor(theme.colors.secondary) + .frame(width: 12, height: 12) + .padding(.vertical, 4) + .padding(.trailing, 16) + .padding(.leading, 4) + .contentShape(Rectangle()) + .onTapGesture { + showOneHandUIAlert = true + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background { if !oneHandUI { Color(uiColor: .systemGray5) } } + .background(ToolbarMaterial.material(toolbarMaterial)) } - .padding() - .background(theme.appColors.sentMessage) - .cornerRadius(12) - .frame(height: dynamicSize(userFont).rowHeight) + .clipShape(Capsule()) .alert(isPresented: $showOneHandUIAlert) { Alert( title: Text("Reachable chat toolbar"), @@ -44,6 +63,22 @@ struct OneHandUICard: View { ) } } + + private func segment(icon: String, text: LocalizedStringKey, isSelected: Bool, action: @escaping () -> Void) -> some View { + HStack(spacing: 8) { + Image(systemName: icon) + .font(.body) + .foregroundColor(isSelected ? theme.colors.secondary : theme.colors.primary) + Text(text) + .font(.subheadline) + .foregroundColor(isSelected ? theme.colors.secondary : theme.colors.onBackground) + } + .padding(.leading, 16) + .padding(.vertical, 4) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .onTapGesture { action() } + } } #Preview { diff --git a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift index 76bdc898d5..56e343588d 100644 --- a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift +++ b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift @@ -110,8 +110,8 @@ struct MigrateToAppGroupView: View { do { resetChatCtrl() try initializeChat(start: true) - onboardingStageDefault.set(.step4_SetNotificationsMode) - chatModel.onboardingStage = .step4_SetNotificationsMode + onboardingStageDefault.set(.step4_NetworkCommitments) + chatModel.onboardingStage = .step4_NetworkCommitments setV3DBMigration(.ready) } catch let error { dbContainerGroupDefault.set(.documents) diff --git a/apps/ios/Shared/Views/Helpers/DetermineWidth.swift b/apps/ios/Shared/Views/Helpers/DetermineWidth.swift index b05ab17089..54e9fe0e80 100644 --- a/apps/ios/Shared/Views/Helpers/DetermineWidth.swift +++ b/apps/ios/Shared/Views/Helpers/DetermineWidth.swift @@ -21,6 +21,19 @@ struct DetermineWidth: View { } } +struct DetermineHeight: View { + typealias Key = MaximumHeightPreferenceKey + var body: some View { + GeometryReader { proxy in + Color.clear + .preference( + key: MaximumHeightPreferenceKey.self, + value: proxy.size.height + ) + } + } +} + struct DetermineWidthImageVideoItem: View { typealias Key = MaximumWidthImageVideoPreferenceKey var body: some View { @@ -41,6 +54,13 @@ struct MaximumWidthPreferenceKey: PreferenceKey { } } +struct MaximumHeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = max(value, nextValue()) + } +} + struct MaximumWidthImageVideoPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 8b982ec0b7..9f2fc833ba 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -86,6 +86,40 @@ func showSheet( } } +func openExternalLink(_ url: URL) { + let s = url.absoluteString + if s.starts(with: "https://simplex.chat/contact#") || (s.starts(with: "https://smp") && s.contains(".simplex.im/a#")) { + ChatModel.shared.appOpenUrl = url + } else { + showAlert( + title: NSLocalizedString("Open external link?", comment: "alert title"), + message: s, + buttonTitle: NSLocalizedString("Open", comment: "alert button"), + buttonAction: { UIApplication.shared.open(url) }, + cancelButton: true + ) + } +} + +struct ExternalLink: View { + let destination: URL + let label: Label + + init(destination: URL, @ViewBuilder label: () -> Label) { + self.destination = destination + self.label = label() + } + + init(_ titleKey: LocalizedStringKey, destination: URL) where Label == Text { + self.destination = destination + self.label = Text(titleKey) + } + + var body: some View { + Button { openExternalLink(destination) } label: { label } + } +} + let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default) let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) diff --git a/apps/ios/Shared/Views/NewChat/AddChannelView.swift b/apps/ios/Shared/Views/NewChat/AddChannelView.swift index 4e9a42971c..477f7eef8e 100644 --- a/apps/ios/Shared/Views/NewChat/AddChannelView.swift +++ b/apps/ios/Shared/Views/NewChat/AddChannelView.swift @@ -10,6 +10,7 @@ import SwiftUI import SimpleXChat struct AddChannelView: View { + @Environment(\.colorScheme) var colorScheme @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @StateObject private var channelRelaysModel = ChannelRelaysModel.shared @@ -45,28 +46,39 @@ struct AddChannelView: View { private func profileStepView() -> some View { List { Group { - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { - ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128) - if profile.image != nil { - Button { - profile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) + HStack(spacing: 0) { + Spacer(minLength: 0) + ZStack(alignment: .center) { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128) + if profile.image != nil { + Button { + profile.image = nil + } label: { + Image(systemName: "multiply") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12) + } } } + editImageButton { showChooseSource = true } + .buttonStyle(BorderlessButtonStyle()) } - editImageButton { showChooseSource = true } - .buttonStyle(BorderlessButtonStyle()) + .padding(.horizontal, 10) // Offsets transparent space built into 3D asset + #if SIMPLEX_ASSETS + Spacer(minLength: 0) + Image(colorScheme == .light ? "create-channel" : "create-channel-light") + .resizable() + .scaledToFit() + .frame(height: 140) + #endif + Spacer(minLength: 0) } - .frame(maxWidth: .infinity, alignment: .center) } .listRowBackground(Color.clear) .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0)) + .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0)) Section { channelNameTextField() @@ -161,7 +173,10 @@ struct AddChannelView: View { private func createChannel() { focusDisplayName = false profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) - profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on)) + profile.groupPreferences = GroupPreferences( + history: GroupPreference(enable: .on), + support: GroupPreference(enable: .off) + ) creationInProgress = true Task { do { @@ -323,24 +338,24 @@ struct AddChannelView: View { .compactSectionSpacing() Section { - Button("Channel link") { + Button("Continue") { if activeCount >= total { showLinkStep = true } else if activeCount > 0 { let actions: [UIAlertAction] = if activeCount + failedCount < total { [ - UIAlertAction(title: NSLocalizedString("Proceed", comment: "alert action"), style: .default) { _ in showLinkStep = true }, + UIAlertAction(title: NSLocalizedString("Continue", comment: "alert action"), style: .default) { _ in showLinkStep = true }, UIAlertAction(title: NSLocalizedString("Wait", comment: "alert action"), style: .cancel) { _ in } ] } else { [ - UIAlertAction(title: NSLocalizedString("Proceed", comment: "alert action"), style: .default) { _ in showLinkStep = true }, + UIAlertAction(title: NSLocalizedString("Continue", comment: "alert action"), style: .default) { _ in showLinkStep = true }, cancelAlertAction ] } showAlert( NSLocalizedString("Not all relays connected", comment: "alert title"), - message: String.localizedStringWithFormat(NSLocalizedString("Channel will start working with %d of %d relays. Proceed?", comment: "alert message"), activeCount, total), + message: String.localizedStringWithFormat(NSLocalizedString("Channel will start working with %d of %d relays. Continue?", comment: "alert message"), activeCount, total), actions: { actions } ) } @@ -352,7 +367,12 @@ struct AddChannelView: View { .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { - Button("Cancel") { cancelChannelCreation(gInfo) } + Button("Delete channel") { showCancelChannelAlert(gInfo) } + } + } + .onDisappear { + if !showLinkStep && m.creatingChannelId == gInfo.id { + showCancelChannelAlert(gInfo) } } .onChange(of: channelRelaysModel.groupRelays) { relays in @@ -414,6 +434,24 @@ struct AddChannelView: View { } } + private func showCancelChannelAlert(_ gInfo: GroupInfo) { + let activeCount = groupRelays.filter { $0.relayStatus == .rsActive && relayMemberConnFailed($0) == nil }.count + let total = groupRelays.count + showAlert( + NSLocalizedString("Cancel creating channel?", comment: "alert title"), + message: String.localizedStringWithFormat( + NSLocalizedString("Your new channel %@ is connected to %d of %d relays.\nIf you cancel, the channel will be deleted - you can create it again.", comment: "alert message"), + gInfo.groupProfile.displayName, activeCount, total + ), + actions: {[ + UIAlertAction(title: NSLocalizedString("Wait", comment: "alert action"), style: .cancel) { _ in }, + UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert action"), style: .destructive) { _ in + cancelChannelCreation(gInfo) + } + ]} + ) + } + // MARK: - Helpers private func showInvalidChannelNameAlert() { diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift index 3a64a955c5..6add190b88 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -26,7 +26,7 @@ struct AddContactLearnMore: View { VStack(alignment: .leading, spacing: 18) { Text("To connect, your contact can scan QR code or use the link in the app.") Text("If you can't meet in person, show QR code in a video call, or share the link.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).") + ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/readme.html#connect-to-friends")!) } .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground(Color.clear) diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 3ce4d1fa40..47afee5f06 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -68,6 +68,7 @@ struct AddGroupView: View { List { Group { HStack(spacing: 0) { + Spacer(minLength: 0) ZStack(alignment: .center) { ZStack(alignment: .topTrailing) { ProfileImage(imageStr: profile.image, iconName: "person.2.circle.fill", size: 128) @@ -86,16 +87,16 @@ struct AddGroupView: View { editImageButton { showChooseSource = true } .buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable } - .frame(maxWidth: .infinity) + .padding(.horizontal, 10) // Offsets transparent space built into 3D asset #if SIMPLEX_ASSETS + Spacer(minLength: 0) Image(colorScheme == .light ? "create-group" : "create-group-light") .resizable() .scaledToFit() .frame(height: 140) - .frame(maxWidth: .infinity) #endif + Spacer(minLength: 0) } - .frame(maxWidth: .infinity) } .listRowBackground(Color.clear) .listRowSeparator(.hidden) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index fab8f8a143..9bcc326a66 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -338,14 +338,6 @@ private struct InviteView: View { HStack(spacing: 8) { let link = connLinkInvitation.simplexChatUri(short: showShortLink) linkTextView(link) - Button { - UIPasteboard.general.string = link - setInvitationUsed() - } label: { - Image(systemName: "doc.on.doc") - .padding(.top, -7) - .padding(.horizontal, 8) - } Button { showShareSheet(items: [link]) setInvitationUsed() diff --git a/apps/ios/Shared/Views/NewChat/OnboardingCards.swift b/apps/ios/Shared/Views/NewChat/OnboardingCards.swift index 913fdf5577..0a0b3c143d 100644 --- a/apps/ios/Shared/Views/NewChat/OnboardingCards.swift +++ b/apps/ios/Shared/Views/NewChat/OnboardingCards.swift @@ -23,17 +23,19 @@ struct OnboardingCardView: View { let action: () -> Void static let lightStops: [Gradient.Stop] = [ - .init(color: Color(red: 0.824, green: 0.910, blue: 1.0), location: 0.0), - .init(color: Color(red: 0.800, green: 0.914, blue: 1.0), location: 0.5), - .init(color: Color(red: 0.875, green: 1.0, blue: 1.0), location: 0.9), - .init(color: Color(red: 1.0, green: 0.988, blue: 0.918), location: 1.0) + .init(color: oklch(0.9219, 0.0431, 249.4), location: 0.0), + .init(color: oklch(0.9198, 0.0471, 240.7), location: 0.5), + .init(color: oklch(0.9772, 0.0358, 196.6), location: 0.9), + .init(color: oklch(0.9829, 0.0104, 70.0), location: 0.95), + .init(color: oklch(0.9886, 0.0272, 99.1), location: 1.0) ] static let darkStops: [Gradient.Stop] = [ - .init(color: Color(red: 0.016, green: 0.039, blue: 0.141), location: 0.4), - .init(color: Color(red: 0.220, green: 0.329, blue: 0.671), location: 0.72), - .init(color: Color(red: 0.659, green: 0.929, blue: 0.953), location: 0.9), - .init(color: Color(red: 1.0, green: 0.965, blue: 0.878), location: 1.0) + .init(color: oklch(0.1578, 0.0609, 267.3), location: 0.4), + .init(color: oklch(0.4729, 0.1574, 267.3), location: 0.72), + .init(color: oklch(0.9024, 0.0760, 202.8), location: 0.9), + .init(color: oklch(0.9384, 0.0354, 65.0), location: 0.95), + .init(color: oklch(0.9744, 0.0370, 88.4), location: 1.0) ] static let gradientAngle: Double = 80.0 * .pi / 180.0 @@ -206,7 +208,7 @@ struct ConnectOnboardingView: View { .font(.largeTitle) .bold() .lineLimit(1) - .minimumScaleFactor(0.75) + .minimumScaleFactor(0.67) .frame(maxWidth: .infinity, alignment: .center) if isLandscape { ZStack(alignment: .leading) { @@ -277,7 +279,7 @@ struct ConnectOnboardingView: View { private var connectWithSomeonePage: some View { GeometryReader { geo in VStack(spacing: 0) { - pageHeader("Connect with someone", showBack: true) + pageHeader("Create your link", showBack: true) Spacer(minLength: 16) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index b5598c1f85..b61b81a46b 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -44,160 +44,147 @@ struct OnboardingButtonStyle: ButtonStyle { } } -private enum OnboardingConditionsViewSheet: Identifiable { - case showConditions - case configureOperators - - var id: String { - switch self { - case .showConditions: return "showConditions" - case .configureOperators: return "configureOperators" - } - } -} - struct OnboardingConditionsView: View { @EnvironmentObject var theme: AppTheme - @State private var serverOperators: [ServerOperator] = [] - @State private var selectedOperatorIds = Set() - @State private var sheetItem: OnboardingConditionsViewSheet? = nil - @State private var notificationsModeNavLinkActive = false - @State private var justOpened = true + @Environment(\.colorScheme) var colorScheme: ColorScheme + @State private var showConditionsSheet = false + var selectedOperatorIds: Set var body: some View { GeometryReader { g in - let v = ScrollView { - VStack(alignment: .leading, spacing: 20) { - Text("Conditions of use") - .font(.largeTitle) - .bold() - .frame(maxWidth: .infinity, alignment: .center) - .padding(.top, 25) + VStack(alignment: .leading, spacing: 10) { + Spacer(minLength: 0) - Spacer() + heroImage().frame(maxWidth: .infinity, minHeight: 80) - VStack(alignment: .leading, spacing: 20) { - Text("Private chats, groups and your contacts are not accessible to server operators.") - .lineSpacing(2) - .frame(maxWidth: .infinity, alignment: .leading) - Text(""" - By using SimpleX Chat you agree to: - - send only legal content in public groups. - - respect other users – no spam. - """) - .lineSpacing(2) - .frame(maxWidth: .infinity, alignment: .leading) + Text("Network commitments") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .fixedSize(horizontal: false, vertical: true) - Button("Privacy policy and conditions of use.") { - sheetItem = .showConditions - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.horizontal, 4) + Text("Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code") + .font(.callout) + .lineSpacing(2) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 4) + .padding(.top, 10) + .fixedSize(horizontal: false, vertical: true) - Spacer() + Text("You commit to:\n- Only legal content in public groups\n- Respect other users - no spam") + .font(.callout) + .lineSpacing(2) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 4) + .padding(.top, 10) + .fixedSize(horizontal: false, vertical: true) - VStack(spacing: 12) { - acceptConditionsButton() - - Button("Configure server operators") { - sheetItem = .configureOperators - } - .frame(minHeight: 40) - } + Button { + showConditionsSheet = true + } label: { + Text("Privacy policy and conditions of use.") + .fontWeight(.medium) + .fixedSize(horizontal: false, vertical: true) } - .padding(25) - .frame(minHeight: g.size.height) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 4) + .padding(.top, 10) + .padding(.bottom, 15) + + Spacer(minLength: 0) + + acceptButton() + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) } - .onAppear { - if justOpened { - serverOperators = ChatModel.shared.conditions.serverOperators - selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) - justOpened = false + .padding(.horizontal, 25) + .padding(.top, 25) + .padding(.bottom, 25) + .frame(minHeight: g.size.height) + } + .frame(maxHeight: .infinity) + .navigationBarHidden(true) + .sheet(isPresented: $showConditionsSheet) { + NavigationView { + VStack { + ConditionsTextView() + .padding() + acceptButton() + .padding(.horizontal, 25) + .padding(.bottom, 20) } - } - .sheet(item: $sheetItem) { item in - switch item { - case .showConditions: - SimpleConditionsView() - .modifier(ThemedBackground(grouped: true)) - case .configureOperators: - ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds) - .modifier(ThemedBackground()) - } - } - .frame(maxHeight: .infinity, alignment: .top) - if #available(iOS 16.4, *) { - v.scrollBounceBehavior(.basedOnSize) - } else { - v + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } + .modifier(ThemedBackground(grouped: true)) } } - .frame(maxHeight: .infinity, alignment: .top) - .navigationBarHidden(true) // necessary on iOS 15 } - private func continueToNextStep() { - onboardingStageDefault.set(.step4_SetNotificationsMode) - notificationsModeNavLinkActive = true - } - - func notificationsModeNavLinkButton(_ button: @escaping (() -> some View)) -> some View { + @ViewBuilder + private func heroImage() -> some View { + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "network-commitments" : "network-commitments-light") + .resizable() + .scaledToFit() + #else ZStack { - button() - - NavigationLink(isActive: $notificationsModeNavLinkActive) { - notificationsModeDestinationView() - } label: { - EmptyView() - } - .frame(width: 1, height: 1) - .hidden() + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.5, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end + ) + Image(systemName: "checkmark.shield") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) } + .aspectRatio(1.5, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + #endif } - private func notificationsModeDestinationView() -> some View { - SetNotificationsMode() - .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground()) - } - - private func acceptConditionsButton() -> some View { - notificationsModeNavLinkButton { - Button { - Task { - do { - let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId - let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds)) + private func acceptButton() -> some View { + Button { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds)) + await MainActor.run { + ChatModel.shared.conditions = r + } + if let enabledOps = enabledOperators(r.serverOperators) { + let r2 = try await setServerOperators(operators: enabledOps) await MainActor.run { - ChatModel.shared.conditions = r + ChatModel.shared.conditions = r2 + completeOnboarding() } - if let enabledOperators = enabledOperators(r.serverOperators) { - let r2 = try await setServerOperators(operators: enabledOperators) - await MainActor.run { - ChatModel.shared.conditions = r2 - continueToNextStep() - } - } else { - await MainActor.run { - continueToNextStep() - } - } - } catch let error { + } else { await MainActor.run { - showAlert( - NSLocalizedString("Error accepting conditions", comment: "alert title"), - message: responseError(error) - ) + completeOnboarding() } } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } } - } label: { - Text("Accept") } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) + } label: { + Text("Accept") } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } + + private func completeOnboarding() { + let m = ChatModel.shared + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete } private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { @@ -222,7 +209,7 @@ struct OnboardingConditionsView: View { if !haveXFTPProxy { op.xftpRoles.proxy = true } ops[firstEnabledIndex] = op return ops - } else { // Shouldn't happen - view doesn't let to proceed if no operators are enabled + } else { return nil } } else { @@ -405,5 +392,5 @@ struct ChooseServerOperatorsInfoView: View { } #Preview { - OnboardingConditionsView() + OnboardingConditionsView(selectedOperatorIds: []) } diff --git a/apps/ios/Shared/Views/Onboarding/ConnectBannerCard.swift b/apps/ios/Shared/Views/Onboarding/ConnectBannerCard.swift index 460ab9b141..87f66a72bb 100644 --- a/apps/ios/Shared/Views/Onboarding/ConnectBannerCard.swift +++ b/apps/ios/Shared/Views/Onboarding/ConnectBannerCard.swift @@ -83,6 +83,7 @@ struct ConnectBannerCard: View { .lineLimit(1) .minimumScaleFactor(0.75) } + .frame(height: 20) .frame(maxWidth: .infinity) .padding(.vertical, 8) .background(ToolbarMaterial.material(toolbarMaterial)) diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 7301c0421d..3c33546436 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -29,45 +29,82 @@ enum UserProfileAlert: Identifiable { let MAX_BIO_LENGTH_BYTES = 160 struct CreateProfile: View { + @Environment(\.colorScheme) var colorScheme @Environment(\.dismiss) var dismiss @EnvironmentObject var theme: AppTheme @State private var displayName: String = "" @State private var profileBio: String = "" @FocusState private var focusDisplayName @State private var alert: UserProfileAlert? + @State private var showChooseSource = false + @State private var showImagePicker = false + @State private var showTakePhoto = false + @State private var chosenImage: UIImage? = nil + @State private var profileImage: String? = nil var body: some View { List { + Group { + HStack(spacing: 0) { + Spacer(minLength: 0) + ZStack(alignment: .center) { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: profileImage, size: 128) + if profileImage != nil { + Button { + profileImage = nil + } label: { + Image(systemName: "multiply") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12) + } + } + } + + editImageButton { showChooseSource = true } + .buttonStyle(BorderlessButtonStyle()) + } + .padding(.horizontal, 10) // Offsets transparent space built into 3D asset + Spacer(minLength: 0) + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "create-profile" : "create-profile-light") + .resizable() + .scaledToFit() + .frame(height: 140) + // No trailing spacer — asset image has empty space on the right + #endif + } + } + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0)) + Section { - TextField("Enter your name…", text: $displayName) - .focused($focusDisplayName) - TextField("Bio", text: $profileBio) - Button { - createProfile() - } label: { - Label("Create profile", systemImage: "checkmark") + ZStack(alignment: .leading) { + let name = displayName.trimmingCharacters(in: .whitespaces) + if name != mkValidName(name) { + Button { + alert = .invalidNameError(validName: mkValidName(name)) + } label: { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } + } else { + Image(systemName: "pencil").foregroundColor(theme.colors.secondary) + } + TextField("Enter your name…", text: $displayName) + .padding(.leading, 36) + .focused($focusDisplayName) + } + ZStack(alignment: .leading) { + Image(systemName: "pencil").foregroundColor(theme.colors.secondary) + TextField("Bio", text: $profileBio) + .padding(.leading, 36) + } + Button(action: createProfile) { + settingsRow("checkmark", color: theme.colors.primary) { Text("Create profile") } } .disabled(!canCreateProfile(displayName) || !bioFitsLimit()) - } header: { - HStack { - Text("Your profile") - .foregroundColor(theme.colors.secondary) - - let name = displayName.trimmingCharacters(in: .whitespaces) - let validName = mkValidName(name) - if name != validName { - Spacer() - validationErrorIndicator { - alert = .invalidNameError(validName: validName) - } - } else if !bioFitsLimit() { - Spacer() - validationErrorIndicator { - showAlert(NSLocalizedString("Bio too large", comment: "alert title")) - } - } - } - .frame(height: 20) } footer: { VStack(alignment: .leading, spacing: 8) { Text("Your profile is stored on your device and only shared with your contacts.") @@ -75,10 +112,42 @@ struct CreateProfile: View { .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity, alignment: .leading) } + .compactSectionSpacing() } .navigationTitle("Create your profile") .modifier(ThemedBackground(grouped: true)) .alert(item: $alert) { a in userProfileAlert(a, $displayName) } + .confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) { + Button("Take picture") { + showTakePhoto = true + } + Button("Choose from library") { + showImagePicker = true + } + } + .fullScreenCover(isPresented: $showTakePhoto) { + ZStack { + Color.black.edgesIgnoringSafeArea(.all) + CameraImagePicker(image: $chosenImage) + } + } + .sheet(isPresented: $showImagePicker) { + LibraryImagePicker(image: $chosenImage) { _ in + await MainActor.run { + showImagePicker = false + } + } + } + .onChange(of: chosenImage) { image in + Task { + let resized: String? = if let image { + await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + nil + } + await MainActor.run { profileImage = resized } + } + } .onAppear() { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { focusDisplayName = true @@ -86,14 +155,6 @@ struct CreateProfile: View { } } - private func validationErrorIndicator(_ onTap: @escaping () -> Void) -> some View { - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .onTapGesture { - onTap() - } - } - private func bioFitsLimit() -> Bool { chatJsonLength(profileBio) <= MAX_BIO_LENGTH_BYTES } @@ -104,7 +165,8 @@ struct CreateProfile: View { let profile = Profile( displayName: displayName.trimmingCharacters(in: .whitespaces), fullName: "", - shortDescr: shortDescr + shortDescr: shortDescr, + image: profileImage ) let m = ChatModel.shared do { @@ -133,61 +195,103 @@ struct CreateProfile: View { struct CreateFirstProfile: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) var colorScheme: ColorScheme @State private var displayName: String = "" @FocusState private var focusDisplayName @State private var nextStepNavLinkActive = false - + @State private var showMigrateSheet = false var body: some View { - let v = VStack(alignment: .leading, spacing: 16) { - VStack(alignment: .center, spacing: 16) { - Text("Create profile") - .font(.largeTitle) - .bold() - .multilineTextAlignment(.center) - - Text("Your profile is stored on your device and only shared with your contacts.") - .font(.callout) - .foregroundColor(theme.colors.secondary) - .multilineTextAlignment(.center) - } - .fixedSize(horizontal: false, vertical: true) - .frame(maxWidth: .infinity) // Ensures it takes up the full width - .padding(.horizontal, 10) - .onTapGesture { focusDisplayName = false } - - HStack { - let name = displayName.trimmingCharacters(in: .whitespaces) - let validName = mkValidName(name) - ZStack(alignment: .trailing) { - TextField("Enter your name…", text: $displayName) - .focused($focusDisplayName) - .padding(.horizontal) - .padding(.trailing, 20) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill(Color(uiColor: .tertiarySystemFill)) + let spacing: CGFloat = 10 + let topPadding: CGFloat = 8 + let padding: CGFloat = 25 + GeometryReader { g in + let v = ScrollView { + VStack(alignment: .center, spacing: spacing) { + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "your-profile" : "your-profile-light") + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + #else + ZStack { + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end ) - if name != validName { - Button { - showAlert(.invalidNameError(validName: validName)) - } label: { - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .padding(.horizontal, 10) - } + Image(systemName: "person.crop.rectangle") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) } + .aspectRatio(1.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + .frame(maxWidth: .infinity) + #endif + + Text("Your profile") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + Text("On your phone, not on servers.") + .font(.title3) + .fontWeight(.medium) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + Text("No account. No phone. No email. No ID.\nThe most secure encryption.") + .font(.footnote) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + profileNameField() + .padding(.top) + .padding(.bottom, 5) + + Spacer(minLength: 0) + + createProfileButton() + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + } + .padding(.horizontal, padding) + .padding(.top, topPadding) + .padding(.bottom, padding) + .frame(minHeight: g.size.height) + } + .onTapGesture { focusDisplayName = false } + .sheet(isPresented: $showMigrateSheet, onDismiss: { m.migrationState = nil }) { + NavigationView { + MigrateToDevice(migrationState: $m.migrationState) + .navigationTitle("Migrate here") + .modifier(ThemedBackground(grouped: true)) } } - .padding(.top) - - Spacer() - - VStack(spacing: 10) { - createProfileButton() - if !focusDisplayName { - onboardingButtonPlaceholder() + if #available(iOS 17, *) { + v.scrollBounceBehavior(.basedOnSize).defaultScrollAnchor(.bottom) + } else if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + if m.migrationState == nil { + m.migrationState = .pasteOrScanLink + } + showMigrateSheet = true + } label: { + HStack(spacing: 4) { + Image(systemName: "tray.and.arrow.down") + Text("Migrate") + .fontWeight(.medium) + } } } } @@ -195,23 +299,40 @@ struct CreateFirstProfile: View { if #available(iOS 16, *) { focusDisplayName = true } else { - // it does not work before animation completes on iOS 15 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { focusDisplayName = true } } } - .padding(.horizontal, 25) - .padding(.bottom, 25) - .frame(maxWidth: .infinity, alignment: .leading) - if #available(iOS 16, *) { - return v.padding(.top, 10) - } else { - return v.padding(.top, 75).ignoresSafeArea(.all, edges: .top) + .frame(maxHeight: .infinity) + } + + private func profileNameField() -> some View { + let name = displayName.trimmingCharacters(in: .whitespaces) + let validName = mkValidName(name) + return ZStack(alignment: .trailing) { + TextField("Enter profile name...", text: $displayName) + .focused($focusDisplayName) + .padding(.horizontal) + .padding(.trailing, name != validName ? 20 : 0) + .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) + .padding(.horizontal, 10) + } + } } } - func createProfileButton() -> some View { + private func createProfileButton() -> some View { ZStack { Button { createProfile() @@ -236,7 +357,7 @@ struct CreateFirstProfile: View { } private func nextStepDestinationView() -> some View { - OnboardingConditionsView() + YourNetworkView() .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index 263b55a42d..e9b9c6b970 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -9,7 +9,7 @@ import SwiftUI -struct HowItWorks: View { +struct OldHowItWorks: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var m: ChatModel var onboarding: Bool @@ -28,7 +28,7 @@ struct HowItWorks: View { 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).") + ExternalLink("Read more in our GitHub repository.", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#readme")!) } } .padding(.bottom) @@ -61,9 +61,57 @@ struct HowItWorks: View { } } -struct HowItWorks_Previews: PreviewProvider { +struct WhySimpleX: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var m: ChatModel + var onboarding: Bool + @Binding var createProfileNavLinkActive: Bool + + var body: some View { + VStack(alignment: .leading) { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Text("You were born without an account") + .font(.title) + .bold() + .padding(.top) + Text("Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life.") + Text("Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible.") + Text("There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected.") + Text("Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign.") + Text("Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public.") + Text("The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it.") + Text("Because we destroyed the power to know who you are. So that your power can never be taken.") + Text("Be free in your network.") + } + } + .padding(.bottom, 16) + + Spacer() + + if onboarding { + createFirstProfileButton() + } + } + .padding(onboarding ? 25 : 16) + .frame(maxHeight: .infinity, alignment: .top) + .modifier(ThemedBackground()) + } + + private func createFirstProfileButton() -> some View { + Button { + dismiss() + createProfileNavLinkActive = true + } label: { + Text("Get started") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + } +} + +struct WhySimpleX_Previews: PreviewProvider { static var previews: some View { - HowItWorks( + WhySimpleX( onboarding: true, createProfileNavLinkActive: Binding.constant(false) ) diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index daef95fbc6..39ccabce04 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -19,17 +19,18 @@ struct OnboardingView: View { case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) .modifier(ThemedBackground()) - case .step2_CreateProfile: // deprecated + case .step2_CreateProfile: CreateFirstProfile() .modifier(ThemedBackground()) case .step3_CreateSimpleXAddress: // deprecated CreateSimpleXAddress() - case .step3_ChooseServerOperators: - OnboardingConditionsView() + case .step3_ChooseServerOperators, + .step4_SetNotificationsMode: // deprecated + YourNetworkView() .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) - case .step4_SetNotificationsMode: - SetNotificationsMode() + case .step4_NetworkCommitments: + OnboardingConditionsView(selectedOperatorIds: Set(ChatModel.shared.conditions.serverOperators.filter { $0.enabled }.map { $0.operatorId })) .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) case .onboardingComplete: EmptyView() @@ -45,10 +46,11 @@ func onboardingButtonPlaceholder() -> some View { // Spec: spec/client/navigation.md#onboardingStage enum OnboardingStage: String, Identifiable { case step1_SimpleXInfo - case step2_CreateProfile // deprecated + case step2_CreateProfile case step3_CreateSimpleXAddress // deprecated - case step3_ChooseServerOperators // changed to simplified conditions - case step4_SetNotificationsMode + case step3_ChooseServerOperators + case step4_SetNotificationsMode // deprecated + case step4_NetworkCommitments case onboardingComplete public var id: Self { self } diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 717405b03b..1a1f1bb68c 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -11,45 +11,39 @@ import SwiftUI import SimpleXChat struct SetNotificationsMode: View { - @EnvironmentObject var m: ChatModel - @State private var notificationMode = NotificationsMode.instant - @State private var showAlert: NotificationAlert? - @State private var showInfo: Bool = false + @Environment(\.dismiss) var dismiss + @Binding var notificationMode: NotificationsMode + @State private var showInfo = false var body: some View { GeometryReader { g in - let v = ScrollView { + ScrollView { VStack(alignment: .center, spacing: 20) { Text("Push notifications") .font(.largeTitle) .bold() .padding(.top, 25) - - infoText() - + + Button { + showInfo = true + } label: { + Label("How it affects privacy", systemImage: "info.circle") + .font(.headline) + } + Spacer() ForEach(NotificationsMode.values) { mode in NtfModeSelector(mode: mode, selection: $notificationMode) } - + Spacer() - + 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 + dismiss() } label: { - if case .off = notificationMode { - Text("Use chat") - } else { - Text("Enable notifications") - } + Text("OK") } .buttonStyle(OnboardingButtonStyle()) onboardingButtonPlaceholder() @@ -58,50 +52,11 @@ struct SetNotificationsMode: View { .padding(25) .frame(minHeight: g.size.height) } - if #available(iOS 16.4, *) { - v.scrollBounceBehavior(.basedOnSize) - } else { - v - } } .frame(maxHeight: .infinity) .sheet(isPresented: $showInfo) { NotificationsInfoView() } - .navigationBarHidden(true) // necessary on iOS 15 - } - - private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { - switch mode { - case .off: - m.tokenStatus = .new - m.notificationMode = .off - default: - Task { - do { - let status = try await apiRegisterToken(token: token, notificationMode: mode) - await MainActor.run { - m.tokenStatus = status - m.notificationMode = mode - } - } catch let error { - let a = getErrorAlert(error, "Error enabling notifications") - AlertManager.shared.showAlertMsg( - title: a.title, - message: a.message - ) - } - } - } - } - - private func infoText() -> some View { - Button { - showInfo = true - } label: { - Label("How it affects privacy", systemImage: "info.circle") - .font(.headline) - } } } @@ -180,6 +135,6 @@ struct NotificationsInfoView: View { struct NotificationsModeView_Previews: PreviewProvider { static var previews: some View { - SetNotificationsMode() + SetNotificationsMode(notificationMode: .constant(.instant)) } } diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index 80f35c1190..15b8e05b5e 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -12,68 +12,84 @@ import SimpleXChat struct SimpleXInfo: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme @Environment(\.colorScheme) var colorScheme: ColorScheme - @State private var showHowItWorks = false + @State private var showWhyBuilt = false @State private var createProfileNavLinkActive = false var onboarding: Bool var body: some View { GeometryReader { g in - let v = ScrollView { - 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) - } - } + VStack(alignment: .center, spacing: 10) { + Image(colorScheme == .light ? "logo" : "logo-light") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: (g.size.width - 50) * 0.55) + .padding(.leading, 4) + .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) - Spacer() + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "intro" : "intro-light") + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + #else + ZStack { + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end + ) + Image(systemName: "bubble.left.and.bubble.right") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) + } + .aspectRatio(1.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + .frame(maxWidth: .infinity) + #endif - VStack(alignment: .leading) { - 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) + Text("Be free\nin your network") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) - Spacer() + Text("Private and secure messaging.") + .font(.title3) + .fontWeight(.medium) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) - if onboarding { - VStack(spacing: 10) { - createFirstProfileButton() + Text("The first network where you own\nyour contacts and groups.") + .font(.footnote) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) - 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) - } + if onboarding { + Spacer(minLength: 0) + + createFirstProfileButton() + .padding(.vertical, 10) + + Button { + showWhyBuilt = true + } label: { + Label("Why SimpleX is built.", systemImage: "info.circle") + .font(.headline) } } - .padding(.horizontal, 25) - .padding(.top, 75) - .padding(.bottom, 25) - .frame(minHeight: g.size.height) } + .padding(.horizontal, 25) + .padding(.top, 28) + .padding(.bottom, 20) + .frame(minHeight: g.size.height) .sheet(isPresented: Binding( - get: { m.migrationState != nil }, + get: { m.migrationState != nil && !createProfileNavLinkActive }, set: { _ in m.migrationState = nil MigrationToDeviceState.save(nil) } @@ -86,17 +102,12 @@ struct SimpleXInfo: View { .modifier(ThemedBackground(grouped: true)) } } - .sheet(isPresented: $showHowItWorks) { - HowItWorks( + .sheet(isPresented: $showWhyBuilt) { + WhySimpleX( onboarding: onboarding, createProfileNavLinkActive: $createProfileNavLinkActive ) } - if #available(iOS 16.4, *) { - v.scrollBounceBehavior(.basedOnSize) - } else { - v - } } .onAppear() { setLastVersionDefault() @@ -105,32 +116,12 @@ struct SimpleXInfo: View { .navigationBarHidden(true) // necessary on iOS 15 } - 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(.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, 12) - } - private func createFirstProfileButton() -> some View { ZStack { Button { createProfileNavLinkActive = true } label: { - Text("Create your profile") + Text("Get started") } .buttonStyle(OnboardingButtonStyle(isDisabled: false)) diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index b7249f42ea..41a342d7c8 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -634,7 +634,7 @@ private let versionDescriptions: [VersionDescription] = [ ), VersionDescription( version: "v6.5", - post: URL(string: "https://simplex.chat/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html"), + post: URL(string: "https://simplex.chat/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html"), features: [ .feature(Description( icon: nil, @@ -791,7 +791,7 @@ struct WhatsNewView: View { } } if let post = v.post { - Link(destination: post) { + ExternalLink(destination: post) { HStack { Text("Read more") Image(systemName: "arrow.up.right.circle") diff --git a/apps/ios/Shared/Views/Onboarding/YourNetwork.swift b/apps/ios/Shared/Views/Onboarding/YourNetwork.swift new file mode 100644 index 0000000000..d3727e196e --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/YourNetwork.swift @@ -0,0 +1,193 @@ +// +// YourNetwork.swift +// SimpleX (iOS) +// +// Created by Evgeny on 22/04/2026. +// Copyright © 2026 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +private enum YourNetworkSheet: Identifiable { + case configureOperators + case configureNotifications + + var id: String { + switch self { + case .configureOperators: return "configureOperators" + case .configureNotifications: return "configureNotifications" + } + } +} + +struct YourNetworkView: View { + @EnvironmentObject var theme: AppTheme + @Environment(\.colorScheme) var colorScheme: ColorScheme + @State private var serverOperators: [ServerOperator] = [] + @State private var selectedOperatorIds = Set() + @State private var notificationMode: NotificationsMode = .instant + @State private var sheetItem: YourNetworkSheet? = nil + @State private var nextStepNavLinkActive = false + @State private var justOpened = true + + var body: some View { + GeometryReader { g in + VStack(alignment: .center, spacing: 10) { + Spacer(minLength: 0) + + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "your-network" : "your-network-light") + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + #else + ZStack { + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end + ) + Image(systemName: "network") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) + } + .aspectRatio(1.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + .frame(maxWidth: .infinity) + #endif + + Text("Your network") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + .padding(.top, 15) + + Text("Network routers cannot know\nwho talks to whom") + .font(.title3) + .fontWeight(.medium) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + VStack(alignment: .leading, spacing: 20) { + configureRoutersButton() + configureNotificationsButton() + } + .padding(.top, 15) + .padding(.bottom, 15) + + Spacer(minLength: 0) + + continueButton() + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + } + .padding(.horizontal, 25) + .padding(.top, 8) + .padding(.bottom, 20) + .frame(minHeight: g.size.height) + } + .onAppear { + if justOpened { + serverOperators = ChatModel.shared.conditions.serverOperators + selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) + justOpened = false + } + } + .sheet(item: $sheetItem) { item in + switch item { + case .configureOperators: + ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds) + .modifier(ThemedBackground()) + case .configureNotifications: + SetNotificationsMode(notificationMode: $notificationMode) + .modifier(ThemedBackground()) + } + } + .frame(maxHeight: .infinity) + .navigationBarHidden(true) + } + + private func configureRoutersButton() -> some View { + Button { + sheetItem = .configureOperators + } label: { + HStack(spacing: 6) { + Text("Setup routers") + .fontWeight(.medium) + ForEach(serverOperators.reversed()) { op in + Image(op.logo(colorScheme)) + .resizable() + .scaledToFit() + .frame(width: 22, height: 22) + .grayscale(selectedOperatorIds.contains(op.operatorId) ? 0.0 : 1.0) + } + } + } + } + + private func configureNotificationsButton() -> some View { + Button { + sheetItem = .configureNotifications + } label: { + HStack(spacing: 4) { + Text("Setup notifications") + .fontWeight(.medium) + Image(systemName: notificationMode.icon) + } + } + } + + private func continueButton() -> some View { + ZStack { + Button { + applyNotificationMode() + onboardingStageDefault.set(.step4_NetworkCommitments) + nextStepNavLinkActive = true + } label: { + Text("Continue") + } + .buttonStyle(OnboardingButtonStyle()) + + NavigationLink(isActive: $nextStepNavLinkActive) { + OnboardingConditionsView(selectedOperatorIds: selectedOperatorIds) + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground()) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func applyNotificationMode() { + let m = ChatModel.shared + if let token = m.deviceToken { + switch notificationMode { + case .off: + m.tokenStatus = .new + m.notificationMode = .off + default: + Task { + do { + let status = try await apiRegisterToken(token: token, notificationMode: notificationMode) + await MainActor.run { + m.tokenStatus = status + m.notificationMode = notificationMode + } + } catch let error { + let a = getErrorAlert(error, "Error enabling notifications") + AlertManager.shared.showAlertMsg( + title: a.title, + message: a.message + ) + } + } + } + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift index 184b03e679..a504b00116 100644 --- a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift +++ b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift @@ -22,14 +22,16 @@ struct DeveloperView: View { VStack { List { Section { - ZStack(alignment: .leading) { - Image(colorScheme == .dark ? "github_light" : "github") - .resizable() - .frame(width: 24, height: 24) - .opacity(0.5) - .colorMultiply(theme.colors.secondary) - Text("Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)") - .padding(.leading, 36) + ExternalLink(destination: URL(string: "https://github.com/simplex-chat/simplex-chat")!) { + ZStack(alignment: .leading) { + Image(colorScheme == .dark ? "github_light" : "github") + .resizable() + .frame(width: 24, height: 24) + .opacity(0.5) + .colorMultiply(theme.colors.secondary) + Text("Install SimpleX Chat for terminal") + .padding(.leading, 36) + } } NavigationLink { TerminalView() diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift index d9862aaac8..f74516c2c8 100644 --- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift @@ -23,7 +23,7 @@ struct IncognitoHelp: View { Text("Incognito mode protects your privacy by using a new random profile for each contact.") Text("It allows having many anonymous connections without any shared data between them in a single chat profile.") Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).") + ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode")!) } .listRowBackground(Color.clear) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift index 6f76e69182..5abbbf8d2e 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift @@ -71,11 +71,7 @@ struct ConditionsWebView: UIViewRepresentable { 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) - } + openExternalLink(url) default: decisionHandler(.allow) } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 74b7374654..f10b945dc0 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -332,7 +332,7 @@ struct UsageConditionsView: View { @ViewBuilder private func conditionsDiffButton(_ font: Font? = nil) -> some View { let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") { - Link(destination: commitUrl) { + ExternalLink(destination: commitUrl) { HStack { Text("Open changes") Image(systemName: "arrow.up.right.circle") @@ -351,21 +351,6 @@ private func regularConditionsHeader() -> some View { } } -struct SimpleConditionsView: View { - - var body: some View { - VStack(alignment: .leading, spacing: 20) { - regularConditionsHeader() - .padding(.top) - .padding(.top) - ConditionsTextView() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal, 25) - .frame(maxHeight: .infinity) - } -} func validateServers_( _ userServers: Binding<[UserOperatorServers]>, diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 9d068d3b26..26f24f2f0f 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -364,11 +364,15 @@ struct OperatorInfoView: View { Text(d) } } - Link(serverOperator.info.website.absoluteString, destination: serverOperator.info.website) + ExternalLink(destination: serverOperator.info.website) { + Text(serverOperator.info.website.absoluteString) + } } if let selfhost = serverOperator.info.selfhost { Section { - Link(selfhost.text, destination: selfhost.link) + ExternalLink(destination: selfhost.link) { + Text(selfhost.text) + } } } } @@ -432,7 +436,7 @@ struct ConditionsTextView: View { private func conditionsLinkView(_ conditionsLink: String) -> some View { VStack(alignment: .leading, spacing: 20) { Text("Current conditions text couldn't be loaded, you can review conditions via this link:") - Link(destination: URL(string: conditionsLink)!) { + ExternalLink(destination: URL(string: conditionsLink)!) { Text(conditionsLink) .multilineTextAlignment(.leading) } @@ -591,11 +595,11 @@ func conditionsLinkButton() -> some View { let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit let mdUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/\(commit)/PRIVACY.md") ?? conditionsURL return Menu { - Link(destination: mdUrl) { + ExternalLink(destination: mdUrl) { Label("Open conditions", systemImage: "doc") } if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") { - Link(destination: commitUrl) { + ExternalLink(destination: commitUrl) { Label("Open changes", systemImage: "ellipsis") } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift index e57df4c5dc..b059be7cb0 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -223,9 +223,7 @@ struct YourServersView: View { func howToButton() -> some View { Button { - DispatchQueue.main.async { - UIApplication.shared.open(howToUrl) - } + openExternalLink(howToUrl) } label: { HStack { Text("How to use your servers") diff --git a/apps/ios/Shared/Views/UserSettings/RTCServers.swift b/apps/ios/Shared/Views/UserSettings/RTCServers.swift index ef891738cc..b045a8ce55 100644 --- a/apps/ios/Shared/Views/UserSettings/RTCServers.swift +++ b/apps/ios/Shared/Views/UserSettings/RTCServers.swift @@ -139,9 +139,7 @@ struct RTCServers: View { func howToButton() -> some View { Button { - DispatchQueue.main.async { - UIApplication.shared.open(howToUrl) - } + openExternalLink(howToUrl) } label: { HStack{ Text("How to") diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 65e34a0ac5..a903329454 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -11,7 +11,7 @@ import SwiftUI import StoreKit import SimpleXChat -let simplexTeamURL = URL(string: "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D")! +let simplexTeamURL = URL(string: "simplex:/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw?h=smp6.simplex.im")! let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String @@ -399,7 +399,9 @@ struct SettingsView: View { } Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) { - settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") } + settingsRow("keyboard", color: theme.colors.secondary) { + ExternalLink("Contribute", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#contribute")!) + } settingsRow("star", color: theme.colors.secondary) { Button("Rate the app") { if let scene = sceneDelegate.windowScene { @@ -407,14 +409,16 @@ struct SettingsView: View { } } } - ZStack(alignment: .leading) { - Image(colorScheme == .dark ? "github_light" : "github") - .resizable() - .frame(width: 24, height: 24) - .opacity(0.5) - .colorMultiply(theme.colors.secondary) - Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)") - .padding(.leading, indent) + ExternalLink(destination: URL(string: "https://github.com/simplex-chat/simplex-chat")!) { + ZStack(alignment: .leading) { + Image(colorScheme == .dark ? "github_light" : "github") + .resizable() + .frame(width: 24, height: 24) + .opacity(0.5) + .colorMultiply(theme.colors.secondary) + Text("Star on GitHub") + .padding(.leading, indent) + } } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index 6c1ea8deb2..ac6ae05984 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -31,7 +31,7 @@ struct UserAddressLearnMore: View { .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).") + ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses")!) .padding(.top) } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 4df58f8b0c..e22042fa24 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -215,11 +215,6 @@ struct UserAddressView: View { HStack(spacing: 8) { let link = userAddress.connLinkContact.simplexChatUri(short: showShortLink) linkTextView(link) - Button { UIPasteboard.general.string = link } label: { - Image(systemName: "doc.on.doc") - .padding(.top, -7) - .padding(.horizontal, 8) - } Button { showShareSheet(items: [link]) } label: { Image(systemName: "square.and.arrow.up") .padding(.top, -7) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 3cf65c8b54..71a7a427be 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -185,9 +185,20 @@ %d месеца time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u пропуснати съобщения. No comment provided by engineer. + + (from owner) + chat link info line + (new) (ново) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (това устройство v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - и още! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Още няколко неща No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Нов контакт @@ -670,9 +715,8 @@ swipe action Активни връзки 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. - Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Добавени сървъри за съобщения No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Допълнителен акцент @@ -859,6 +907,14 @@ swipe action Всички профили profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Всички доклади за нарушения ще бъдат архивирани за вас. @@ -919,6 +975,10 @@ swipe action Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Позволи реакции на съобщения само ако вашият контакт ги разрешава. @@ -934,6 +994,10 @@ swipe action Позволи изпращането на лични съобщения до членовете. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Разреши изпращането на изчезващи съобщения. @@ -944,6 +1008,10 @@ swipe action Позволи споделяне No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Позволи необратимо изтриване на изпратените съобщения. (24 часа) @@ -1049,11 +1117,6 @@ swipe action Отговор на повикване No comment provided by engineer. - - Anybody can host servers. - Протокол и код с отворен код – всеки може да оперира собствени сървъри. - No comment provided by engineer. - App build: %@ Компилация на приложението: %@ @@ -1258,6 +1321,19 @@ swipe action Лош хеш на съобщението No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls По-добри обаждания @@ -1407,6 +1483,10 @@ swipe action И вие, и вашият контакт можете да изпращате гласови съобщения. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1419,7 +1499,7 @@ swipe action Business address Бизнес адрес - No comment provided by engineer. + chat link info line Business chats @@ -1441,15 +1521,6 @@ swipe action Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - С използването на SimpleX Chat вие се съгласявате със: -- изпращане само на легално съдържание в публични групи. -- уважение към другите потребители – без спам. - No comment provided by engineer. - Call already ended! Разговорът вече приключи! @@ -1610,12 +1681,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1630,6 +1710,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1642,6 +1726,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Чат @@ -1761,7 +1849,8 @@ set passcode view Chat with admins Чат с администраторите - chat toolbar + chat feature +chat toolbar Chat with member @@ -1778,11 +1867,23 @@ set passcode view Чатове No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Чатове с членовете No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Проверявай за съобщенията на всеки 20 минути. @@ -1950,11 +2051,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Конфигуриране на сървърни оператори - No comment provided by engineer. - Confirm Потвърди @@ -2060,6 +2156,10 @@ This is your own one-time link! Свърване чрез линк new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Свързване чрез еднократен линк за връзка @@ -2138,7 +2238,7 @@ This is your own one-time link! Connection error (AUTH) Грешка при свързване (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2193,6 +2293,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows Контактът позволява @@ -2258,6 +2362,11 @@ This is your own one-time link! Продължи No comment provided by engineer. + + Contribute + Допринеси + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2285,11 +2394,6 @@ This is your own one-time link! Поправи име на %@? alert message - - Create - Създаване - No comment provided by engineer. - Create 1-time link Създаване на еднократна препратка @@ -2356,11 +2460,19 @@ This is your own one-time link! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Създай своя профил No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2536,11 +2648,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - Децентрализиран - No comment provided by engineer. - Decode link relay test step @@ -2914,6 +3021,14 @@ alert button Личните съобщения между членовете са забранени в тази група. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Деактивиране (запазване на промените) @@ -3014,6 +3129,10 @@ alert button Не изпращай история на нови членове. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -3106,6 +3225,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Редактирай @@ -3127,7 +3250,7 @@ chat item action Enable Активирай - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3162,6 +3285,10 @@ chat item action Разреши достъпа до камерата No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -3181,16 +3308,15 @@ chat item action Активирай незабавни известия? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Активирай заключване No comment provided by engineer. - - Enable notifications - Активирай известията - No comment provided by engineer. - Enable periodic notifications? Активирай периодични известия? @@ -3323,6 +3449,10 @@ chat item action Въведете парола по-горе, за да се покаже! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3355,7 +3485,7 @@ chat item action Error Грешка при свързване със сървъра - No comment provided by engineer. + conn error description Error aborting address change @@ -3674,6 +3804,10 @@ chat item action Грешка при настройването на потвърждениeто за доставка!! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Грешка при стартиране на чата @@ -4015,6 +4149,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -4151,6 +4289,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4207,7 +4349,7 @@ Error: %2$@ Group link Групов линк - No comment provided by engineer. + chat link info line Group links @@ -4316,6 +4458,10 @@ Error: %2$@ Историята не се изпраща на нови членове. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Как работи SimpleX @@ -4410,11 +4556,6 @@ Error: %2$@ Веднага No comment provided by engineer. - - Immune to spam - Защитен от спам и злоупотреби - No comment provided by engineer. - Import Импортиране @@ -4552,9 +4693,9 @@ More improvements are coming soon! Първоначална роля No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Инсталирайте SimpleX Chat за терминал No comment provided by engineer. @@ -4606,7 +4747,7 @@ More improvements are coming soon! Invalid connection link Невалиден линк за връзка - No comment provided by engineer. + conn error description Invalid display name! @@ -4670,6 +4811,10 @@ More improvements are coming soon! Покани членове No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4863,6 +5008,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Нека да поговорим в SimpleX Chat @@ -4883,6 +5032,10 @@ This is your link for group %@! Свържете мобилни и настолни приложения! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Настройки на запомнени настолни устройства @@ -5052,6 +5205,10 @@ This is your link for group %@! Членовете на групата могат да добавят реакции към съобщенията. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) @@ -5202,6 +5359,14 @@ This is your link for group %@! Съобщенията от %@ ще бъдат показани! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5228,16 +5393,15 @@ This is your link for group %@! Съобщенията, файловете и разговорите са защитени чрез **квантово устойчиво e2e криптиране** с перфектна секретност при препращане, правдоподобно опровержение и възстановяване при взлом. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Мигрирай устройството No comment provided by engineer. - - Migrate from another device - Мигриране от друго устройство - No comment provided by engineer. - Migrate here Мигрирай тук @@ -5355,6 +5519,10 @@ This is your link for group %@! Мрежа и сървъри No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection Мрежова връзка @@ -5364,6 +5532,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5377,6 +5549,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Мрежови настройки @@ -5391,6 +5568,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Нов kод за достъп @@ -5482,6 +5663,15 @@ This is your link for group %@! Не No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Приложението няма kод за достъп @@ -5622,9 +5812,16 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5682,7 +5879,7 @@ This is your link for group %@! OK ОК - No comment provided by engineer. + alert button Off @@ -5701,11 +5898,19 @@ new chat action Стара база данни No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Линк за еднократна покана No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5725,6 +5930,10 @@ Requires compatible VPN. Няма се използват Onion хостове. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5822,7 +6031,8 @@ Requires compatible VPN. Open Отвори - alert action + alert action +alert button Open Settings @@ -5855,6 +6065,10 @@ Requires compatible VPN. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -5914,6 +6128,13 @@ Requires compatible VPN. Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -5933,6 +6154,10 @@ Requires compatible VPN. Или сигурно споделете този линк към файла No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Или покажи този код @@ -5942,6 +6167,10 @@ Requires compatible VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5964,6 +6193,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING бройка @@ -6018,6 +6251,10 @@ Requires compatible VPN. Постави изображение No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Поставете линк, за да се свържете! @@ -6201,13 +6438,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Поверителността преосмислена + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -6272,9 +6508,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Актуализацията на профила ще бъде изпратена до вашите контакти. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6282,6 +6517,10 @@ Error: %@ Забрани аудио/видео разговорите. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Забрани необратимото изтриване на съобщения. @@ -6311,6 +6550,10 @@ Error: %@ Забрани изпращането на лични съобщения до членовете. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Забрани изпращането на изчезващи съобщения. @@ -6371,6 +6614,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push известия @@ -6410,24 +6657,14 @@ Enable in *Network & servers* settings. Прочетете още 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. + Прочетете повече в Ръководство за потребителя. No comment provided by engineer. - - 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). - 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). + + Read more in our GitHub repository. + Прочетете повече в нашето GitHub хранилище. No comment provided by engineer. @@ -6590,6 +6827,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Реле сървър се използва само ако е необходимо. Друга страна може да наблюдава вашия IP адрес. @@ -6604,6 +6845,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Премахване @@ -6860,6 +7105,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6902,6 +7151,10 @@ chat item action Запази и уведоми членовете на групата No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -7086,6 +7339,10 @@ chat item action Код за сигурност No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Избери @@ -7205,6 +7462,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Изпрати от галерия или персонализирани клавиатури. @@ -7215,6 +7476,10 @@ chat item action Изпращане до последните 100 съобщения на нови членове. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7229,6 +7494,10 @@ chat item action Подателят може да е изтрил заявката за връзка. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Изпращането на потвърждениe за доставка ще бъде активирано за всички контакти във всички видими чат профили. @@ -7464,6 +7733,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Променете формата на профилните изображения @@ -7497,11 +7774,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Сподели адреса с контактите? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7536,9 +7816,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Сподели с контактите + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7767,6 +8050,11 @@ report reason Квадрат, кръг или нещо между тях. No comment provided by engineer. + + Star on GitHub + Звезда в GitHub + No comment provided by engineer. + Start chat Започни чат @@ -7866,6 +8154,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7874,6 +8166,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -7951,6 +8279,10 @@ Relay address was used to set up this relay for the channel. Направи снимка No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7963,10 +8295,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8000,6 +8328,10 @@ Relay address was used to set up this relay for the channel. Докосни за инкогнито вход No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Докосни за поставяне на линк за връзка @@ -8096,6 +8428,10 @@ It can happen because of some bug or when the connection is compromised.QR кодът, който сканирахте, не е SimpleX линк за връзка. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -8120,9 +8456,9 @@ It can happen because of some bug or when the connection is compromised.Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване! No comment provided by engineer. - - The future of messaging - Ново поколение поверителни съобщения + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8157,6 +8493,10 @@ It can happen because of some bug or when the connection is compromised.Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8197,6 +8537,14 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -8312,6 +8660,10 @@ It can happen because of some bug or when the connection is compromised.Скриване на нежелани съобщения. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection За да направите нова връзка @@ -8390,10 +8742,6 @@ You will be prompted to complete authentication before this feature is enabled.< За да проверите криптирането от край до край с вашия контакт, сравнете (или сканирайте) кода на вашите устройства. No comment provided by engineer. - - Toggle chat list: - No comment provided by engineer. - Toggle incognito when connecting. Избор на инкогнито при свързване. @@ -8407,6 +8755,10 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total No comment provided by engineer. @@ -8570,13 +8922,17 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. На новите членове се изпращат до последните 100 съобщения. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Актуализация @@ -8687,11 +9043,6 @@ To connect, please ask your contact to create another connection link and check Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - Използвай чата - No comment provided by engineer. - Use current profile Използвай текущия профил @@ -8768,6 +9119,10 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8914,6 +9269,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Изчакване на настолно устройство… @@ -8952,6 +9311,10 @@ To connect, please ask your contact to create another connection link and check Предупреждение: Може да загубите някои данни! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE сървъри @@ -9000,6 +9363,10 @@ To connect, please ask your contact to create another connection link and check Когато споделяте инкогнито профил с някого, този профил ще се използва за групите, в които той ви кани. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9246,6 +9613,12 @@ Repeat join request? Не може да изпращате съобщения! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9255,11 +9628,6 @@ Repeat join request? Не можахте да бъдете потвърдени; Моля, опитайте отново. No comment provided by engineer. - - You decide who can connect. - Хората могат да се свържат с вас само чрез ликовете, които споделяте. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9323,6 +9691,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9454,6 +9826,10 @@ Repeat connection request? Вашите контакти ще останат свързани. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9472,6 +9848,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Вашите настройки @@ -9511,6 +9891,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Вашият автоматично генериран профил @@ -9538,21 +9922,11 @@ Relays can access channel messages. Вашите настройки No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Допринеси](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Изпратете ни имейл](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Звезда в GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_курсив_ @@ -9700,6 +10074,10 @@ marked deleted chat item preview text повикване… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -10332,6 +10710,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address премахнат адрес за контакт @@ -10636,6 +11018,10 @@ last received msg: %2$@ \~зачеркнат~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index e7d0fc2d4b..1cc44dd7cb 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -185,9 +185,20 @@ %d měsíce time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u zpráv přeskočeno. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nový) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (toto zařízení v%@) @@ -445,6 +480,12 @@ channel relay bar progress with errors - a více! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -542,6 +583,10 @@ time interval Ještě pár věcí No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Nový kontakt @@ -665,9 +710,8 @@ swipe action Aktivní spojení 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. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -732,6 +776,10 @@ swipe action Přidané servery zpráv No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Další zbarvení @@ -848,6 +896,14 @@ swipe action Všechny profily profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -904,6 +960,10 @@ swipe action Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Povolit reakce na zprávy, pokud je váš kontakt povolí. @@ -919,6 +979,10 @@ swipe action Povolit odesílání přímých zpráv členům. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Povolit odesílání mizících zpráv. @@ -929,6 +993,10 @@ swipe action Povolit sdílení No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Povolit nevratné smazání odeslaných zpráv. (24 hodin) @@ -1034,11 +1102,6 @@ swipe action 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: %@ @@ -1233,6 +1296,21 @@ swipe action Špatný hash zprávy No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Buďte svobodní ve své síti. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít. + No comment provided by engineer. + Better calls Lepší volání @@ -1372,6 +1450,10 @@ swipe action Hlasové zprávy můžete posílat vy i váš kontakt. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1384,7 +1466,7 @@ swipe action Business address Obchodní adresa - No comment provided by engineer. + chat link info line Business chats @@ -1403,12 +1485,6 @@ swipe action Podle profilu chatu (výchozí) nebo [podle připojení](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! Hovor již skončil! @@ -1569,12 +1645,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1589,6 +1674,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1601,6 +1690,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat No comment provided by engineer. @@ -1710,7 +1803,8 @@ set passcode view Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1725,10 +1819,22 @@ set passcode view Chaty No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1876,10 +1982,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm Potvrdit @@ -1974,6 +2076,10 @@ Toto je váš vlastní jednorázový odkaz! Připojte se prostřednictvím odkazu new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Připojit se jednorázovým odkazem @@ -2042,7 +2148,7 @@ Toto je váš vlastní jednorázový odkaz! Connection error (AUTH) Chyba spojení (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2091,6 +2197,10 @@ Toto je váš vlastní jednorázový odkaz! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kontakt povolil @@ -2156,6 +2266,11 @@ Toto je váš vlastní jednorázový odkaz! Pokračovat No comment provided by engineer. + + Contribute + Přispějte + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2182,11 +2297,6 @@ Toto je váš vlastní jednorázový odkaz! Correct name to %@? alert message - - Create - Vytvořit - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2250,11 +2360,19 @@ Toto je váš vlastní jednorázový odkaz! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Vytvořte si profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2426,11 +2544,6 @@ Toto je váš vlastní jednorázový odkaz! Debug delivery No comment provided by engineer. - - Decentralized - Decentralizované - No comment provided by engineer. - Decode link relay test step @@ -2798,6 +2911,14 @@ alert button Přímé zprávy mezi členy jsou v této skupině zakázány. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Vypnout (zachovat přepsání) @@ -2895,6 +3016,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -2983,6 +3108,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Upravit @@ -3004,7 +3133,7 @@ chat item action Enable Zapnout - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3038,6 +3167,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -3056,16 +3189,15 @@ chat item action Povolit okamžitá oznámení? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Povolit zámek No comment provided by engineer. - - Enable notifications - Povolit upozornění - No comment provided by engineer. - Enable periodic notifications? Povolit pravidelná oznámení? @@ -3192,6 +3324,10 @@ chat item action Zadejte heslo do hledání! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3222,7 +3358,7 @@ chat item action Error Chyba - No comment provided by engineer. + conn error description Error aborting address change @@ -3536,6 +3672,10 @@ chat item action Chyba nastavování potvrzení o doručení! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Chyba při spuštění chatu @@ -3869,6 +4009,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -3999,6 +4143,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4053,7 +4201,7 @@ Error: %2$@ Group link Odkaz na skupinu - No comment provided by engineer. + chat link info line Group links @@ -4161,6 +4309,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Jak SimpleX funguje @@ -4254,11 +4406,6 @@ Error: %2$@ Ihned No comment provided by engineer. - - Immune to spam - Odolná vůči spamu a zneužití - No comment provided by engineer. - Import Import @@ -4389,9 +4536,9 @@ More improvements are coming soon! Počáteční role No comment provided by engineer. - - 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) + + Install SimpleX Chat for terminal + Nainstalujte SimpleX Chat pro terminál No comment provided by engineer. @@ -4442,7 +4589,7 @@ More improvements are coming soon! Invalid connection link Neplatný odkaz na spojení - No comment provided by engineer. + conn error description Invalid display name! @@ -4501,6 +4648,10 @@ More improvements are coming soon! Pozvat členy No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4688,6 +4839,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Promluvme si v SimpleX Chatu @@ -4707,6 +4862,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -4874,6 +5033,10 @@ This is your link for group %@! Členové skupin mohou přidávat reakce na zprávy. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) @@ -5020,6 +5183,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5044,12 +5215,12 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device + + Migrate device No comment provided by engineer. @@ -5163,6 +5334,10 @@ This is your link for group %@! Síť a servery No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection No comment provided by engineer. @@ -5171,6 +5346,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5183,6 +5362,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Nastavení sítě @@ -5197,6 +5381,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nové heslo @@ -5287,6 +5475,15 @@ This is your link for group %@! Ne No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Žádné heslo aplikace @@ -5426,9 +5623,18 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - Bez uživatelských identifikátorů. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén. No comment provided by engineer. @@ -5484,7 +5690,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5503,11 +5709,19 @@ new chat action Stará databáze No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Jednorázový zvací odkaz No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5527,6 +5741,10 @@ Vyžaduje povolení sítě VPN. Onion hostitelé nebudou použiti. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5624,7 +5842,8 @@ Vyžaduje povolení sítě VPN. Open Otevřít - alert action + alert action +alert button Open Settings @@ -5657,6 +5876,10 @@ Vyžaduje povolení sítě VPN. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -5713,6 +5936,13 @@ Vyžaduje povolení sítě VPN. Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -5729,6 +5959,10 @@ Vyžaduje povolení sítě VPN. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5737,6 +5971,10 @@ Vyžaduje povolení sítě VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5758,6 +5996,10 @@ Vyžaduje povolení sítě VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Počet PING @@ -5811,6 +6053,10 @@ Vyžaduje povolení sítě VPN. Vložit obrázek No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5988,13 +6234,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Nové vymezení soukromí + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -6057,9 +6302,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Aktualizace profilu bude zaslána vašim kontaktům. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6067,6 +6311,10 @@ Error: %@ Zákaz audio/video hovorů. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Zakázat nevratné mazání zpráv. @@ -6095,6 +6343,10 @@ Error: %@ Zakázat odesílání přímých zpráv členům. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Zakázat posílání mizících zpráv. @@ -6155,6 +6407,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Nabízená oznámení @@ -6192,24 +6448,14 @@ Enable in *Network & servers* settings. Přečíst více No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Více informací v [průvodci uživatele](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Více informací v průvodci uživatele. No comment provided by engineer. - - 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). - 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). + + Read more in our GitHub repository. + Přečtěte si více v našem GitHub repozitáři. No comment provided by engineer. @@ -6370,6 +6616,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Přenosový server se používá pouze v případě potřeby. Jiná strana může sledovat vaši IP adresu. @@ -6384,6 +6634,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Odstranit @@ -6636,6 +6890,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6677,6 +6935,10 @@ chat item action Uložit a upozornit členy skupiny No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6855,6 +7117,10 @@ chat item action Bezpečnostní kód No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Vybrat @@ -6974,6 +7240,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Odeslat je z galerie nebo vlastní klávesnice. @@ -6983,6 +7253,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -6997,6 +7271,10 @@ chat item action Odesílatel možná smazal požadavek připojení. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Odesílání potvrzení o doručení bude povoleno pro všechny kontakty ve všech viditelných profilech chatu. @@ -7230,6 +7508,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -7262,11 +7548,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Sdílet adresu s kontakty? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7300,9 +7589,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Sdílet s kontakty + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7526,6 +7818,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + Hvězda na GitHubu + No comment provided by engineer. + Start chat Začít chat @@ -7622,6 +7919,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7630,6 +7931,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -7707,6 +8044,10 @@ Relay address was used to set up this relay for the channel. Vyfotit No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7719,10 +8060,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7755,6 +8092,10 @@ Relay address was used to set up this relay for the channel. Klepnutím se připojíte inkognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7848,6 +8189,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -7872,9 +8217,9 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Š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 + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -7909,6 +8254,11 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Stará databáze nebyla během přenášení odstraněna, lze ji smazat. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -7948,6 +8298,16 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -8058,6 +8418,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Vytvoření nového připojení @@ -8136,10 +8500,6 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních. No comment provided by engineer. - - Toggle chat list: - No comment provided by engineer. - Toggle incognito when connecting. Změnit inkognito režim při připojení. @@ -8153,6 +8513,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Toolbar opacity No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total No comment provided by engineer. @@ -8308,12 +8672,16 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Aktualizovat @@ -8422,11 +8790,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - Použijte chat - No comment provided by engineer. - Use current profile Použít aktuální profil @@ -8500,6 +8863,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8638,6 +9005,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8674,6 +9045,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Upozornění: můžete ztratit nějaká data! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC servery ICE @@ -8720,6 +9095,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -8910,7 +9289,7 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte. + Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud odkaz později smažete. No comment provided by engineer. @@ -8950,6 +9329,12 @@ Repeat join request? Nemůžete posílat zprávy! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -8959,11 +9344,6 @@ Repeat join request? Nemohli jste být ověřeni; Zkuste to prosím znovu. No comment provided by engineer. - - 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 already requested connection! Repeat connection request? @@ -9025,6 +9405,11 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + Narodili jste se bez účtu. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9155,6 +9540,11 @@ Repeat connection request? Vaše kontakty zůstanou připojeny. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9173,6 +9563,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Vaše preference @@ -9211,6 +9605,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Váš náhodný profil @@ -9238,21 +9636,11 @@ Relays can access channel messages. Vaše nastavení No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Přispějte](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Pošlete nám e-mail](mailto:chat@simplex.chat) 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. - \_italic_ \_kurzíva_ @@ -9393,6 +9781,10 @@ marked deleted chat item preview text volání… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -10019,6 +10411,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10309,6 +10705,10 @@ last received msg: %2$@ \~stávka~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 5840cff078..872fafddd7 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -185,9 +185,23 @@ %d Monate time interval - - %d relays - channel relay bar + + %d relays failed + %d Relais fehlgeschlagen + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d Relais nicht aktiv + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d Relais entfernt + channel relay bar +channel subscriber relay bar %d sec @@ -206,10 +220,12 @@ %d subscriber + %d Abonnent channel subscriber count %d subscribers + %d Abonnenten channel subscriber count @@ -219,21 +235,45 @@ %1$d/%2$d relays active + %1$d/%2$d Relais aktiv channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d Relais aktiv, %3$d Fehler + channel relay bar + %1$d/%2$d relays active, %3$d failed + %1$d/%2$d Relais aktiv, %3$d fehlgeschlagen channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d Relais aktiv, %3$d entfernt + channel relay bar %1$d/%2$d relays connected + %1$d/%2$d Relais verbunden channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + %1$d/%2$d Relais verbunden, %3$d Fehler + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d Relais verbunden, %3$d fehlgeschlagen + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d Relais verbunden, %3$d entfernt + channel subscriber relay bar %lld @@ -247,6 +287,7 @@ channel relay bar progress with errors %lld channel events + %lld Kanalereignisse No comment provided by engineer. @@ -349,11 +390,21 @@ channel relay bar progress with errors %u übersprungene Nachrichten. No comment provided by engineer. + + (from owner) + (vom Eigentümer) + chat link info line + (new) (Neu) No comment provided by engineer. + + (signed) + (signiert) + chat link info line + (this device v%@) (Dieses Gerät hat v%@) @@ -401,6 +452,7 @@ channel relay bar progress with errors **Test relay** to retrieve its name. + **Relais testen** um seinen Namen abzurufen. No comment provided by engineer. @@ -446,6 +498,15 @@ channel relay bar progress with errors - und mehr! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - Opt‑in zum Senden von Linkvorschauen. +- Hyperlink‑Phishing verhindern. +- Link‑Tracking entfernen. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +605,11 @@ time interval Ein paar weitere Dinge No comment provided by engineer. + + A link for one person to connect + Verbindungs-Link für eine Person + No comment provided by engineer. + A new contact Ein neuer Kontakt @@ -670,9 +736,9 @@ swipe action Aktive Verbindungen 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. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre SimpleX-Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre SimpleX-Kontakte gesendet. No comment provided by engineer. @@ -740,6 +806,11 @@ swipe action Nachrichtenserver hinzugefügt No comment provided by engineer. + + Adding relays will be supported later. + Das Hinzufügen von Relais wird zu einem späteren Zeitpunkt unterstützt. + No comment provided by engineer. + Additional accent Erste Akzentfarbe @@ -782,7 +853,7 @@ swipe action Admins can create the links to join groups. - Administratoren können Links für den Beitritt zu Gruppen erzeugen. + Administratoren können Links für den Beitritt zu Gruppen erstellen. No comment provided by engineer. @@ -860,6 +931,16 @@ swipe action Alle Profile profile dropdown + + All relays failed + Alle Relais fehlgeschlagen + No comment provided by engineer. + + + All relays removed + Alle Relais entfernt + No comment provided by engineer. + All reports will be archived for you. Alle Meldungen werden für Sie archiviert. @@ -920,6 +1001,11 @@ swipe action Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden) No comment provided by engineer. + + Allow members to chat with admins. + Mitgliedern den Chat mit Administratoren erlauben. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Erlauben Sie Reaktionen auf Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt. @@ -935,6 +1021,11 @@ swipe action Das Senden von Direktnachrichten an Gruppenmitglieder erlauben. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Das Senden von Direktnachrichten an Abonnenten erlauben. + No comment provided by engineer. + Allow sending disappearing messages. Das Senden von verschwindenden Nachrichten erlauben. @@ -945,6 +1036,11 @@ swipe action Teilen erlauben No comment provided by engineer. + + Allow subscribers to chat with admins. + Abonnenten den Chat mit Administratoren erlauben. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden) @@ -1050,11 +1146,6 @@ swipe action 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: %@ @@ -1260,6 +1351,23 @@ swipe action Ungültiger Nachrichten-Hash No comment provided by engineer. + + Be free +in your network + Seien Sie frei +in Ihrem Netzwerk + No comment provided by engineer. + + + Be free in your network. + Genießen Sie die Freiheit in Ihrem Netzwerk. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Weil wir die Macht zerstört haben, zu wissen, wer Sie sind. Damit Ihnen Ihre Macht niemals genommen werden kann. + No comment provided by engineer. + Better calls Verbesserte Anrufe @@ -1357,6 +1465,7 @@ swipe action Block subscriber for all? + Abonnent für alle blockieren? No comment provided by engineer. @@ -1409,8 +1518,14 @@ swipe action Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden. No comment provided by engineer. + + Bottom bar + Untere Leiste + No comment provided by engineer. + Broadcast + Broadcast compose placeholder for channel owner @@ -1421,7 +1536,7 @@ swipe action Business address Geschäftliche Adresse - No comment provided by engineer. + chat link info line Business chats @@ -1443,15 +1558,6 @@ swipe action Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden: -- nur legale Inhalte in öffentlichen Gruppen zu versenden. -- andere Nutzer zu respektieren - kein Spam. - No comment provided by engineer. - Call already ended! Anruf ist bereits beendet! @@ -1602,48 +1708,80 @@ set passcode view Channel + Kanal No comment provided by engineer. Channel display name + Anzeigename des Kanals No comment provided by engineer. Channel full name (optional) + Vollständiger Kanalname (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + Der Kanal hat keine aktiven Relais. Bitte später erneut versuchen. + alert message +alert subtitle + Channel image + Kanalbild No comment provided by engineer. Channel link + Kanallink + chat link info line + + + Channel preferences + Kanal-Präferenzen No comment provided by engineer. Channel profile + Kanalprofil No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + Das Kanalprofil wird auf den Geräten der Abonnenten und auf den Chat‑Relais gespeichert. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + Das Kanalprofil wurde geändert. Beim Speichern wird das aktualisierte Profil an die Abonnenten des Kanals gesendet. alert message + + Channel temporarily unavailable + Der Kanal ist vorübergehend nicht erreichbar + alert title + Channel will be deleted for all subscribers - this cannot be undone! + Der Kanal wird für alle Abonnenten gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + Der Kanal wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + Der Kanal wird mit %1$d von %2$d Relais gestartet. Fortfahren? alert message + + Channels + Kanäle + No comment provided by engineer. + Chat Chat @@ -1731,18 +1869,22 @@ set passcode view Chat relay + Chat-Relais No comment provided by engineer. Chat relays + Chat-Relais No comment provided by engineer. Chat relays forward messages in channels you create. + Chat‑Relais leiten Nachrichten in den von Ihnen erstellten Kanälen weiter. No comment provided by engineer. Chat relays forward messages to channel subscribers. + Chat‑Relais leiten Nachrichten an Kanal-Abonnenten weiter. No comment provided by engineer. @@ -1763,7 +1905,8 @@ set passcode view Chat with admins Chat mit Administratoren - chat toolbar + chat feature +chat toolbar Chat with member @@ -1780,11 +1923,26 @@ set passcode view Chats No comment provided by engineer. + + Chats with admins are prohibited. + Chats mit Administratoren sind nicht erlaubt. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Chats mit Administratoren in öffentlichen Kanälen sind nicht Ende‑zu‑Ende‑verschlüsselt – bitte nur über vertrauenswürdige Chat‑Relais nutzen. + alert message + Chats with members Chats mit Mitgliedern No comment provided by engineer. + + Chats with members are disabled + Chats mit Mitgliedern sind deaktiviert + No comment provided by engineer. + Check messages every 20 min. Alle 20min Nachrichten überprüfen. @@ -1797,10 +1955,12 @@ set passcode view Check relay address and try again. + Relais-Adresse überprüfen und erneut versuchen. alert message Check relay name and try again. + Relais-Name überprüfen und erneut versuchen. alert message @@ -1950,11 +2110,7 @@ set passcode view Configure relays - No comment provided by engineer. - - - Configure server operators - Server-Betreiber konfigurieren + Relais konfigurieren No comment provided by engineer. @@ -2062,6 +2218,11 @@ Das ist Ihr eigener Einmal-Link! Über einen Link verbinden new chat sheet title + + Connect via link or QR code + Über einen Link oder QR-Code verbinden + No comment provided by engineer. + Connect via one-time link Über einen Einmal-Link verbinden @@ -2140,7 +2301,7 @@ Das ist Ihr eigener Einmal-Link! Connection error (AUTH) Verbindungsfehler (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2360,11 @@ Das ist Ihr eigener Einmal-Link! Verbindungen No comment provided by engineer. + + Contact address + Kontaktadresse + chat link info line + Contact allows Der Kontakt erlaubt @@ -2269,6 +2435,11 @@ Das ist Ihr eigener Einmal-Link! Weiter No comment provided by engineer. + + Contribute + Unterstützen Sie uns + No comment provided by engineer. + Conversation deleted! Chat-Inhalte entfernt! @@ -2299,11 +2470,6 @@ Das ist Ihr eigener Einmal-Link! Richtiger Name für %@? alert message - - Create - Erstellen - No comment provided by engineer. - Create 1-time link Einmal-Link erstellen @@ -2316,7 +2482,7 @@ Das ist Ihr eigener Einmal-Link! Create a group using a random profile. - Erstellen Sie eine Gruppe mit einem zufälligen Profil. + Gruppe mit einem zufälligen Profil erstellen. No comment provided by engineer. @@ -2336,7 +2502,7 @@ Das ist Ihr eigener Einmal-Link! Create link - Link erzeugen + Link erstellen No comment provided by engineer. @@ -2356,15 +2522,17 @@ Das ist Ihr eigener Einmal-Link! Create public channel + Öffentlichen Kanal erstellen No comment provided by engineer. Create public channel (BETA) + Öffentlichen Kanal erstellen (BETA) No comment provided by engineer. Create queue - Erzeuge Warteschlange + Warteschlange erstellen server test step @@ -2372,9 +2540,19 @@ Das ist Ihr eigener Einmal-Link! Ihre Adresse erstellen No comment provided by engineer. + + Create your link + Ihren Link erstellen + No comment provided by engineer. + Create your profile - Erstellen Sie Ihr Profil + Ihr Profil erstellen + No comment provided by engineer. + + + Create your public address + Ihre öffentliche Adresse erstellen No comment provided by engineer. @@ -2399,6 +2577,7 @@ Das ist Ihr eigener Einmal-Link! Creating channel + Kanal wird erstellt No comment provided by engineer. @@ -2559,13 +2738,9 @@ Das ist Ihr eigener Einmal-Link! Debugging-Zustellung No comment provided by engineer. - - Decentralized - Dezentral - No comment provided by engineer. - Decode link + Link dekodieren relay test step @@ -2616,10 +2791,12 @@ swipe action Delete channel + Kanal löschen No comment provided by engineer. Delete channel? + Kanal löschen? No comment provided by engineer. @@ -2795,6 +2972,7 @@ alert button Delete relay + Relais löschen No comment provided by engineer. @@ -2962,6 +3140,16 @@ alert button In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Direktnachrichten zwischen Abonnenten sind nicht erlaubt. + No comment provided by engineer. + + + Disable + Deaktivieren + alert button + Disable (keep overrides) Deaktivieren (vorgenommene Einstellungen bleiben erhalten) @@ -3067,6 +3255,11 @@ alert button Den Nachrichtenverlauf nicht an neue Mitglieder senden. No comment provided by engineer. + + Do not send history to new subscribers. + Den Nachrichtenverlauf nicht an neue Abonnenten senden. + No comment provided by engineer. + Do not use credentials with proxy. Verwenden Sie keine Anmeldeinformationen mit einem Proxy. @@ -3168,6 +3361,11 @@ chat item action E2E-verschlüsselte Benachrichtigungen. No comment provided by engineer. + + Easier to invite your friends 👋 + Freunde einladen – jetzt noch einfacher 👋 + No comment provided by engineer. + Edit Bearbeiten @@ -3175,6 +3373,7 @@ chat item action Edit channel profile + Kanalprofil bearbeiten No comment provided by engineer. @@ -3190,7 +3389,7 @@ chat item action Enable Aktivieren - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3214,6 +3413,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Aktivieren Sie mindestens ein Chat‑Relais unter 'Netzwerk & Server'. channel creation warning @@ -3226,6 +3426,11 @@ chat item action Kamera-Zugriff aktivieren No comment provided by engineer. + + Enable chats with admins? + Chats mit Administratoren aktivieren? + alert title + Enable disappearing messages by default. Verschwindende Nachrichten sind per Voreinstellung aktiviert. @@ -3246,16 +3451,16 @@ chat item action Sofortige Benachrichtigungen aktivieren? No comment provided by engineer. + + Enable link previews? + Linkvorschau aktivieren? + alert title + Enable lock Sperre aktivieren No comment provided by engineer. - - Enable notifications - Benachrichtigungen aktivieren - No comment provided by engineer. - Enable periodic notifications? Periodische Benachrichtigungen aktivieren? @@ -3363,6 +3568,7 @@ chat item action Enter channel name… + Kanalname eingeben… No comment provided by engineer. @@ -3390,8 +3596,14 @@ chat item action Für die Anzeige das Passwort im Suchfeld eingeben! No comment provided by engineer. + + Enter profile name... + Profilname eingeben... + No comment provided by engineer. + Enter relay name… + Relais-Name eingeben… No comment provided by engineer. @@ -3422,7 +3634,7 @@ chat item action Error Fehler - No comment provided by engineer. + conn error description Error aborting address change @@ -3451,6 +3663,7 @@ chat item action Error adding relay + Fehler beim Hinzufügen des Relais alert title @@ -3515,6 +3728,7 @@ chat item action Error creating channel + Fehler beim Erstellen des Kanals alert title @@ -3699,6 +3913,7 @@ chat item action Error saving channel profile + Fehler beim Speichern des Kanalprofils No comment provided by engineer. @@ -3766,6 +3981,11 @@ chat item action Fehler beim Setzen von Empfangsbestätigungen! No comment provided by engineer. + + Error sharing channel + Fehler beim Teilen des Kanals + alert title + Error starting chat Fehler beim Starten des Chats @@ -4134,6 +4354,11 @@ server test error Für alle Moderatoren No comment provided by engineer. + + For anyone to reach you + Damit Sie jeder erreichen kann + No comment provided by engineer. + For chat profile %@: Für das Chat-Profil %@: @@ -4281,6 +4506,7 @@ Fehler: %2$@ Get link + Link erhalten relay test step @@ -4288,6 +4514,11 @@ Fehler: %2$@ Bei Erwähnung benachrichtigt werden. No comment provided by engineer. + + Get started + Jetzt starten + No comment provided by engineer. + Good afternoon! Guten Nachmittag! @@ -4346,7 +4577,7 @@ Fehler: %2$@ Group link Gruppen-Link - No comment provided by engineer. + chat link info line Group links @@ -4458,6 +4689,11 @@ Fehler: %2$@ Der Nachrichtenverlauf wird nicht an neue Gruppenmitglieder gesendet. No comment provided by engineer. + + History is not sent to new subscribers. + Der Nachrichtenverlauf wird nicht an neue Abonnenten gesendet. + No comment provided by engineer. + How SimpleX works Wie SimpleX funktioniert @@ -4558,11 +4794,6 @@ Fehler: %2$@ Sofort No comment provided by engineer. - - Immune to spam - Immun gegen Spam und Missbrauch - No comment provided by engineer. - Import Importieren @@ -4705,9 +4936,9 @@ Weitere Verbesserungen sind bald verfügbar! Anfängliche Rolle No comment provided by engineer. - - 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) + + Install SimpleX Chat for terminal + Installieren Sie SimpleX Chat als Terminalanwendung No comment provided by engineer. @@ -4765,7 +4996,7 @@ Weitere Verbesserungen sind bald verfügbar! Invalid connection link Ungültiger Verbindungslink - No comment provided by engineer. + conn error description Invalid display name! @@ -4789,10 +5020,12 @@ Weitere Verbesserungen sind bald verfügbar! Invalid relay address! + Ungültige Relais-Adresse! alert title Invalid relay name! + Ungültiger Relais-Name! alert title @@ -4830,6 +5063,11 @@ Weitere Verbesserungen sind bald verfügbar! Mitglieder einladen No comment provided by engineer. + + Invite someone privately + Für privaten Chat einladen + No comment provided by engineer. + Invite to chat Zum Chat einladen @@ -4908,6 +5146,7 @@ Weitere Verbesserungen sind bald verfügbar! Join channel + Kanal beitreten No comment provided by engineer. @@ -4999,10 +5238,12 @@ Das ist Ihr Link für die Gruppe %@! Leave channel + Kanal verlassen No comment provided by engineer. Leave channel? + Kanal verlassen? No comment provided by engineer. @@ -5030,6 +5271,11 @@ Das ist Ihr Link für die Gruppe %@! Weniger Datenverkehr in mobilen Netzen. No comment provided by engineer. + + Let someone connect to you + Jemand mit Ihnen verbinden lassen + No comment provided by engineer. + Let's talk in SimpleX Chat Lassen Sie uns in SimpleX Chat kommunizieren @@ -5050,6 +5296,11 @@ Das ist Ihr Link für die Gruppe %@! Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗 No comment provided by engineer. + + Link signature verified. + Linksignatur erfolgreich überprüft. + owner verification + Linked desktop options Verknüpfte Desktop-Optionen @@ -5235,6 +5486,11 @@ Das ist Ihr Link für die Gruppe %@! Gruppenmitglieder können eine Reaktion auf Nachrichten geben. No comment provided by engineer. + + Members can chat with admins. + Mitglieder können mit Administratoren chatten. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) @@ -5302,6 +5558,7 @@ Das ist Ihr Link für die Gruppe %@! Message error + Übertragungsfehler No comment provided by engineer. @@ -5399,6 +5656,16 @@ Das ist Ihr Link für die Gruppe %@! Die Nachrichten von %@ werden angezeigt! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + Nachrichten in diesem Kanal sind **nicht Ende‑zu‑Ende‑verschlüsselt**. Chat‑Relais können diese Nachrichten sehen. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + Nachrichten in diesem Kanal sind nicht Ende‑zu‑Ende‑verschlüsselt. Chat‑Relais können diese Nachrichten sehen. + E2EE info chat item + Messages in this chat will never be deleted. Nachrichten in diesem Chat werden nie gelöscht. @@ -5429,16 +5696,16 @@ Das ist Ihr Link für die Gruppe %@! Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt. No comment provided by engineer. + + Migrate + Migrieren + No comment provided by engineer. + Migrate device Gerät migrieren No comment provided by engineer. - - Migrate from another device - Von einem anderen Gerät migrieren - No comment provided by engineer. - Migrate here Hierher migrieren @@ -5559,6 +5826,11 @@ Das ist Ihr Link für die Gruppe %@! Netzwerk & Server No comment provided by engineer. + + Network commitments + Netzwerk Verpflichtungen + No comment provided by engineer. + Network connection Netzwerkverbindung @@ -5569,6 +5841,11 @@ Das ist Ihr Link für die Gruppe %@! Dezentralisiertes Netzwerk No comment provided by engineer. + + Network error + Netzwerk-Fehler + conn error description + Network issues - message expired after many attempts to send it. Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen. @@ -5584,6 +5861,13 @@ Das ist Ihr Link für die Gruppe %@! Netzwerk-Betreiber No comment provided by engineer. + + Network routers cannot know +who talks to whom + Netzwerk‑Router können nicht erkennen, +wer mit wem kommuniziert + No comment provided by engineer. + Network settings Netzwerkeinstellungen @@ -5599,6 +5883,11 @@ Das ist Ihr Link für die Gruppe %@! Neu token status text + + New 1-time link + Neuer Einmal-Link + No comment provided by engineer. + New Passcode Neuer Zugangscode @@ -5626,6 +5915,7 @@ Das ist Ihr Link für die Gruppe %@! New chat relay + Neues Chat-Relais No comment provided by engineer. @@ -5698,6 +5988,18 @@ Das ist Ihr Link für die Gruppe %@! Nein No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + Kein Account. Keine Telefonnummer. Keine E‑Mail. Keine ID. +Die sicherste Verschlüsselung. + No comment provided by engineer. + + + No active relays + Keine aktiven Relais + No comment provided by engineer. + No app password Kein App-Passwort @@ -5705,10 +6007,12 @@ Das ist Ihr Link für die Gruppe %@! No chat relays + Keine Chat-Relais No comment provided by engineer. No chat relays enabled. + Es sind keine Chat-Relais aktiviert. servers warning @@ -5856,13 +6160,24 @@ Das ist Ihr Link für die Gruppe %@! Keine ungelesenen Chats No comment provided by engineer. - - No user identifiers. - Keine Benutzerkennungen. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Niemand verfolgte Ihre Gespräche. Niemand erstellte eine Karte, wo Sie sich aufgehalten haben. Privatsphäre war nie ein Feature - sie war selbstverständlich. + No comment provided by engineer. + + + Non-profit governance + Non‑Profit‑Governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nicht ein besseres Schloss an der Tür eines Anderen. Kein freundlicher Vermieter, der Ihre Privatsphäre respektiert, aber dennoch jeden Besucher registriert. Sie sind kein Gast. Sie sind zu Hause. Kein Vermieter, kein Fremder kann es betreten - Sie sind souverän. No comment provided by engineer. Not all relays connected + Es sind nicht alle Relais verbunden alert title @@ -5922,7 +6237,7 @@ Das ist Ihr Link für die Gruppe %@! OK OK - No comment provided by engineer. + alert button Off @@ -5941,11 +6256,21 @@ new chat action Alte Datenbank No comment provided by engineer. + + On your phone, not on servers. + Auf Ihrem Gerät, nicht auf Servern. + No comment provided by engineer. + One-time invitation link Einmal-Einladungslink No comment provided by engineer. + + One-time link + Einmal-Link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,9 +6290,14 @@ Dies erfordert die Aktivierung eines VPNs. Onion-Hosts werden nicht verwendet. No comment provided by engineer. + + Only channel owners can change channel preferences. + Kanal-Präferenzen können nur von Kanal-Eigentümern geändert werden. + No comment provided by engineer. + Only chat owners can change preferences. - Nur Chat-Eigentümer können die Präferenzen ändern. + Präferenzen können nur von Chat-Eigentümern geändert werden. No comment provided by engineer. @@ -6068,7 +6398,8 @@ Dies erfordert die Aktivierung eines VPNs. Open Öffnen - alert action + alert action +alert button Open Settings @@ -6082,6 +6413,7 @@ Dies erfordert die Aktivierung eines VPNs. Open channel + Kanal öffnen new chat action @@ -6104,6 +6436,11 @@ Dies erfordert die Aktivierung eines VPNs. Nutzungsbedingungen öffnen No comment provided by engineer. + + Open external link? + Externen Link öffnen? + alert title + Open full link Vollständigen Link öffnen @@ -6126,6 +6463,7 @@ Dies erfordert die Aktivierung eines VPNs. Open new channel + Neuen Kanal öffnen new chat action @@ -6173,6 +6511,17 @@ Dies erfordert die Aktivierung eines VPNs. Betreiber-Server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Betreiber verpflichten sich: +- Unabhängig zu bleiben +- Metadaten auf ein Minimum zu reduzieren +- Geprüften Open‑Source‑Code einzusetzen + No comment provided by engineer. + Or import archive file Oder importieren Sie eine Archiv-Datei @@ -6193,6 +6542,11 @@ Dies erfordert die Aktivierung eines VPNs. Oder teilen Sie diesen Datei-Link sicher No comment provided by engineer. + + Or show QR in person or via video call. + Oder den QR‑Code persönlich oder per Videoanruf zeigen. + No comment provided by engineer. + Or show this code Oder diesen QR-Code anzeigen @@ -6203,6 +6557,11 @@ Dies erfordert die Aktivierung eines VPNs. Oder zum privaten Teilen No comment provided by engineer. + + Or use this QR - print or show online. + Oder diesen QR‑Code verwenden – ausgedruckt oder online. + No comment provided by engineer. + Organize chats into lists Chats in Listen verwalten @@ -6222,10 +6581,17 @@ Dies erfordert die Aktivierung eines VPNs. Owner + Eigentümer No comment provided by engineer. Owners + Eigentümer + No comment provided by engineer. + + + Ownership: you can run your own relays. + Volle Kontrolle: Sie können Ihre eigenen Relais betreiben. No comment provided by engineer. @@ -6283,6 +6649,11 @@ Dies erfordert die Aktivierung eines VPNs. Bild einfügen No comment provided by engineer. + + Paste link / Scan + Link einfügen / Scannen + No comment provided by engineer. + Paste link to connect! Zum Verbinden den Link einfügen! @@ -6439,10 +6810,12 @@ Fehler: %@ Preset relay address + Voreingestellte Relais-Adresse No comment provided by engineer. Preset relay name + Voreingestellter Relais-Name No comment provided by engineer. @@ -6480,14 +6853,14 @@ Fehler: %@ Datenschutz- und Nutzungsbedingungen. No comment provided by engineer. - - Privacy redefined - Datenschutz neu definiert + + Privacy: for owners and subscribers. + Privatsphäre: für Besitzer und Abonnenten. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. + + Private and secure messaging. + Private und sichere Kommunikation. No comment provided by engineer. @@ -6532,6 +6905,7 @@ Fehler: %@ Proceed + Fortfahren alert action @@ -6559,9 +6933,9 @@ Fehler: %@ Profil-Design No comment provided by engineer. - - Profile update will be sent to your contacts. - Profil-Aktualisierung wird an Ihre Kontakte gesendet. + + Profile update will be sent to your SimpleX contacts. + Profil-Aktualisierung wird an Ihre SimpleX-Kontakte gesendet. alert message @@ -6569,6 +6943,11 @@ Fehler: %@ Audio-/Video-Anrufe nicht erlauben. No comment provided by engineer. + + Prohibit chats with admins. + Chat mit Administratoren nicht erlauben. + No comment provided by engineer. + Prohibit irreversible message deletion. Unwiederbringliches löschen von Nachrichten nicht erlauben. @@ -6599,6 +6978,11 @@ Fehler: %@ Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + Das Senden von Direktnachrichten an Abonnenten nicht erlauben. + No comment provided by engineer. + Prohibit sending disappearing messages. Das Senden von verschwindenden Nachrichten nicht erlauben. @@ -6666,6 +7050,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Der Proxy benötigt ein Passwort No comment provided by engineer. + + Public channels - speak freely 🚀 + Öffentliche Kanäle – frei sprechen 🚀 + No comment provided by engineer. + Push notifications Push-Benachrichtigungen @@ -6706,24 +7095,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Mehr erfahren 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. + Lesen Sie mehr dazu im Benutzerhandbuch. No comment provided by engineer. - - 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. - 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. + + Read more in our GitHub repository. + Erfahren Sie in unserem GitHub-Repository mehr dazu. No comment provided by engineer. @@ -6885,20 +7264,29 @@ swipe action Relay + Relais No comment provided by engineer. Relay address + Relais-Adresse alert title Relay connection failed + Relais-Verbindung fehlgeschlagen alert title Relay link + Relais-Link No comment provided by engineer. + + Relay results: + Relay‑Status: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden. @@ -6911,6 +7299,12 @@ swipe action Relay test failed! + Relais-Test fehlgeschlagen! + No comment provided by engineer. + + + Reliability: many relays per channel. + Zuverlässigkeit: mehrere Relais pro Kanal. No comment provided by engineer. @@ -6955,10 +7349,12 @@ swipe action Remove subscriber + Abonnent entfernen No comment provided by engineer. Remove subscriber? + Abonnent entfernen? alert title @@ -7196,6 +7592,11 @@ swipe action SOCKS-Proxy No comment provided by engineer. + + Safe web links + Sichere Web-Links + No comment provided by engineer. + Safely receive files Dateien sicher herunterladen @@ -7224,6 +7625,7 @@ chat item action Save (and notify subscribers) + Speichern (Abonnenten benachrichtigen) alert button @@ -7241,6 +7643,11 @@ chat item action Speichern und Gruppenmitglieder benachrichtigen No comment provided by engineer. + + Save and notify subscribers + Speichern und Abonnenten benachrichtigen + No comment provided by engineer. + Save and reconnect Speichern und neu verbinden @@ -7253,10 +7660,12 @@ chat item action Save channel profile + Kanalprofil speichern No comment provided by engineer. Save channel profile? + Kanalprofil speichern? alert title @@ -7439,6 +7848,11 @@ chat item action Sicherheitscode No comment provided by engineer. + + Security: owners hold channel keys. + Sicherheit: Eigentümer besitzen die Kanalschlüssel. + No comment provided by engineer. + Select Auswählen @@ -7456,7 +7870,7 @@ chat item action Selected chat preferences prohibit this message. - Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. + Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt. No comment provided by engineer. @@ -7516,7 +7930,7 @@ chat item action Send link previews - Link-Vorschau senden + Linkvorschau senden No comment provided by engineer. @@ -7569,6 +7983,11 @@ chat item action Anfrage ohne Nachricht senden No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Den Link über einen beliebigen Messenger versenden – es ist sicher. Bitte in SimpleX einfügen. + No comment provided by engineer. + Send them from gallery or custom keyboards. Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen. @@ -7579,6 +7998,11 @@ chat item action Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Bis zu 100 der letzten Nachrichten an neue Abonnenten senden. + No comment provided by engineer. + Send your private feedback to groups. Senden Sie Ihr privates Feedback an Gruppen. @@ -7594,6 +8018,11 @@ chat item action Der Absender hat möglicherweise die Verbindungsanfrage gelöscht. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Das Senden einer Link-Vorschau kann Ihre IP‑Adresse an die Website übermitteln. Sie können dies später in den Datenschutzeinstellungen ändern. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Das Senden von Empfangsbestätigungen an alle Kontakte in allen sichtbaren Chat-Profilen wird aktiviert. @@ -7721,6 +8150,7 @@ chat item action Server requires authorization to connect to relay, check password. + Der Server erfordert eine Autorisierung, um eine Verbindung zum Relais herzustellen. Bitte Passwort überprüfen. relay test error @@ -7853,6 +8283,16 @@ chat item action Die Einstellungen wurden geändert. alert message + + Setup notifications + Benachrichtigungen einrichten + No comment provided by engineer. + + + Setup routers + Router einrichten + No comment provided by engineer. + Shape profile images Form der Profil-Bilder @@ -7889,11 +8329,16 @@ chat item action Die Adresse öffentlich teilen No comment provided by engineer. - - Share address with contacts? - Die Adresse mit Kontakten teilen? + + Share address with SimpleX contacts? + Die Adresse mit SimpleX-Kontakten teilen? alert title + + Share channel + Kanal teilen + No comment provided by engineer. + Share from other apps. Aus anderen Apps heraus teilen. @@ -7921,6 +8366,7 @@ chat item action Share relay address + Relais-Adresse teilen No comment provided by engineer. @@ -7933,9 +8379,14 @@ chat item action Mit SimpleX teilen No comment provided by engineer. - - Share with contacts - Mit Kontakten teilen + + Share via chat + Per Chat teilen + No comment provided by engineer. + + + Share with SimpleX contacts + Mit SimpleX-Kontakten teilen No comment provided by engineer. @@ -8065,7 +8516,7 @@ chat item action SimpleX channel link - SimpleX-Kanal-Link + SimpleX-Kanallink simplex link type @@ -8110,6 +8561,7 @@ chat item action SimpleX relay address + SimpleX Relais-Adresse simplex link type @@ -8185,6 +8637,11 @@ report reason Quadratisch, kreisförmig oder irgendetwas dazwischen. No comment provided by engineer. + + Star on GitHub + Stern auf GitHub vergeben + No comment provided by engineer. + Start chat Starten Sie den Chat @@ -8287,19 +8744,74 @@ report reason Subscriber + Abonnent No comment provided by engineer. + + Subscriber reports + Abonnenten-Meldungen + chat feature + Subscriber will be removed from channel - this cannot be undone! + Abonnent wird aus dem Kanal entfernt. Dies kann nicht rückgängig gemacht werden! alert message Subscribers + Abonnenten + No comment provided by engineer. + + + Subscribers can add message reactions. + Abonnenten können eine Reaktion auf Nachrichten geben. + No comment provided by engineer. + + + Subscribers can chat with admins. + Abonnenten können mit Administratoren chatten. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Abonnenten können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Abonnenten können Nachrichten an Moderatoren melden. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Abonnenten können SimpleX-Links versenden. + No comment provided by engineer. + + + Subscribers can send direct messages. + Abonnenten können Direktnachrichten versenden. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Abonnenten können verschwindende Nachrichten versenden. + No comment provided by engineer. + + + Subscribers can send files and media. + Abonnenten können Dateien und Medien versenden. + No comment provided by engineer. + + + Subscribers can send voice messages. + Abonnenten können Sprachnachrichten versenden. No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Abonnenten verbinden sich über den Relais‑Link mit dem Kanal. +Die Relais-Adresse wurde zur Einrichtung dieses Relais für diesen Kanal verwendet. No comment provided by engineer. @@ -8382,6 +8894,11 @@ Relay address was used to set up this relay for the channel. Machen Sie ein Foto No comment provided by engineer. + + Talk to someone + Mit jemandem sprechen + No comment provided by engineer. + Tap Connect to chat Verbinden tippen, um zu chatten @@ -8397,13 +8914,9 @@ Relay address was used to set up this relay for the channel. Verbinden tippen, um den Bot zu nutzen. No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen. - No comment provided by engineer. - Tap Join channel + Tippen, um dem Kanal beizutreten No comment provided by engineer. @@ -8423,7 +8936,7 @@ Relay address was used to set up this relay for the channel. Tap to activate profile. - Zum Aktivieren des Profils tippen. + Tippen, um das Profil zu aktivieren. No comment provided by engineer. @@ -8436,9 +8949,14 @@ Relay address was used to set up this relay for the channel. Zum Inkognito beitreten tippen No comment provided by engineer. + + Tap to open + Zum Öffnen tippen + No comment provided by engineer. + Tap to paste link - Zum Link einfügen tippen + Tippen, um den Link einzufügen No comment provided by engineer. @@ -8464,6 +8982,7 @@ server test failure Test relay + Relais testen No comment provided by engineer. @@ -8520,6 +9039,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The app removed this message after %lld attempts to receive it. + Die App hat diese Nachricht nach %lld Empfangsversuchen entfernt. No comment provided by engineer. @@ -8537,6 +9057,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der von Ihnen gescannte Code ist kein SimpleX-Link-QR-Code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + Die Verbindung hat das Limit für nicht zugestellte Nachrichten erreicht + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline. @@ -8562,9 +9087,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro 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 + + The first network where you own +your contacts and groups. + Das erste Netzwerk, +in dem Sie Ihre Kontakte und Gruppen besitzen. No comment provided by engineer. @@ -8602,6 +9129,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Die älteste Freiheit des Menschen - mit einem anderen Menschen sprechen zu können, ohne beobachtet zu werden - gestützt auf einer Infrastruktur, die Sie nicht verraten kann. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. @@ -8647,6 +9179,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Design No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Dann sind wir online gegangen, und jede Plattform wollte Etwas von Ihnen - Ihren Namen, Ihre Nummer, Ihre Freunde. Wir akzeptierten, dass es der Preis mit Anderen zu kommunizieren ist, Jemandem preiszugeben, mit wem und wie wir miteinander kommunizieren. Jede Generation, Menschen und Technologien, kannten es nur so - Telefon, E-Mail, Messenger, soziale Medien. Es schien der einzig mögliche Weg zu sein. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Es gibt einen anderen Weg. Ein Netzwerk ohne Telefonnummern, ohne Benutzernamen, ohne Benutzerkennungen und ohne jegliche Benutzeridentität. Ein Netzwerk, welches Menschen verbindet und verschlüsselte Nachrichten überträgt, ohne zu wissen, wer mit wem verbunden ist. + No comment provided by engineer. + These conditions will also apply for: **%@**. Diese Nutzungsbedingungen gelten auch für: **%@**. @@ -8714,10 +9256,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This is a chat relay address, it cannot be used to connect. + Dies ist eine Chat‑Relais-Adresse, welche nicht zum Verbinden verwendet werden kann. alert message This is your link for channel %@! + Dies ist Ihr Link für den Kanal %@! new chat action @@ -8770,6 +9314,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Um unerwünschte Nachrichten zu verbergen. No comment provided by engineer. + + To make SimpleX Network last. + Für ein dauerhaftes SimpleX-Netzwerk. + No comment provided by engineer. + To make a new connection Um eine Verbindung mit einem neuen Kontakt zu erstellen @@ -8857,11 +9406,6 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen. No comment provided by engineer. - - Toggle chat list: - Chat-Liste umschalten: - No comment provided by engineer. - Toggle incognito when connecting. Inkognito beim Verbinden einschalten. @@ -8877,6 +9421,11 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Deckkraft der Symbolleiste No comment provided by engineer. + + Top bar + Obere Leiste + No comment provided by engineer. + Total Summe aller Abonnements @@ -8944,6 +9493,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Unblock subscriber for all? + Abonnent für alle freigeben? No comment provided by engineer. @@ -9046,13 +9596,18 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Unsupported connection link Verbindungs-Link wird nicht unterstützt - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Bis zu 100 der letzten Nachrichten werden an neue Abonnenten gesendet. + No comment provided by engineer. + Update Aktualisieren @@ -9178,11 +9733,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s TCP-Port 443 nur für voreingestellte Server verwenden. No comment provided by engineer. - - Use chat - Verwenden Sie Chat - No comment provided by engineer. - Use current profile Aktuelles Profil nutzen @@ -9200,6 +9750,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use for new channels + Für neue Kanäle verwenden No comment provided by engineer. @@ -9244,6 +9795,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use relay + Relais verwenden No comment provided by engineer. @@ -9266,6 +9818,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Die App mit einer Hand bedienen. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Diese Adresse in Ihrem Social‑Media‑Profil, auf Ihrer Webseite oder in Ihrer E‑Mail‑Signatur verwenden. + No comment provided by engineer. + Use web port Web-Port nutzen @@ -9288,6 +9845,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verify + Überprüfen relay test step @@ -9412,12 +9970,19 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wait + Abwarten alert action Wait response + Antwort abwarten relay test step + + Waiting for channel owner to add relays. + Warte auf das Hinzufügen von Relais durch den Eigentümer des Kanals. + No comment provided by engineer. + Waiting for desktop... Es wird auf den Desktop gewartet... @@ -9458,6 +10023,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Warnung: Sie könnten einige Daten verlieren! No comment provided by engineer. + + We made connecting simpler for new users. + Wir haben das Verbinden für neue Nutzer vereinfacht. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE-Server @@ -9508,6 +10078,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s 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. No comment provided by engineer. + + Why SimpleX is built. + Warum SimpleX entwickelt wurde. + No comment provided by engineer. + WiFi WiFi @@ -9722,6 +10297,7 @@ Verbindungsanfrage wiederholen? You can share a link or a QR code - anybody will be able to join the channel. + Sie können einen Link oder QR-Code teilen - damit kann jeder dem Kanal beitreten. No comment provided by engineer. @@ -9769,8 +10345,18 @@ Verbindungsanfrage wiederholen? Sie können keine Nachrichten versenden! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + Sie verpflichten sich dazu: +- nur legale Inhalte in öffentlichen Gruppen zu versenden +- andere Nutzer zu respektieren - kein Spam + No comment provided by engineer. + You connected to the channel via this relay link. + Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden. No comment provided by engineer. @@ -9778,11 +10364,6 @@ Verbindungsanfrage wiederholen? Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut. No comment provided by engineer. - - You decide who can connect. - Sie entscheiden, wer sich mit Ihnen verbinden kann. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9850,6 +10431,11 @@ Verbindungsanfrage wiederholen? Sie sollten Benachrichtigungen erhalten. token info + + You were born without an account + Sie wurden ohne eine Benutzerkennung geboren. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Sie können erst dann Nachrichten versenden, **sobald Ihre Anfrage angenommen wurde**. @@ -9887,6 +10473,7 @@ Verbindungsanfrage wiederholen? You will stop receiving messages from this channel. Chat history will be preserved. + Sie werden keine Nachrichten mehr aus diesem Kanal erhalten. Der Chatverlauf bleibt erhalten. No comment provided by engineer. @@ -9936,6 +10523,7 @@ Verbindungsanfrage wiederholen? Your channel + Ihr Kanal No comment provided by engineer. @@ -9988,6 +10576,11 @@ Verbindungsanfrage wiederholen? Ihre Kontakte bleiben weiterhin verbunden. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Ihre Kommunikation gehört Ihnen, so wie es immer war, bevor es das Internet gab. Das Netzwerk ist kein Ort, den Sie besuchen. Es ist ein Ort, den Sie erschaffen und besitzen und Niemand kann es Ihnen nehmen, egal ob Sie es privat oder öffentlich machen. + No comment provided by engineer. + Your credentials may be sent unencrypted. Ihre Anmeldeinformationen können unverschlüsselt versendet werden. @@ -10008,6 +10601,11 @@ Verbindungsanfrage wiederholen? Ihre Gruppe No comment provided by engineer. + + Your network + Ihr Netzwerk + No comment provided by engineer. + Your preferences Ihre Präferenzen @@ -10026,6 +10624,8 @@ Verbindungsanfrage wiederholen? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + Ihr Profil **%@** wird mit Kanal‑Relais und Abonnenten geteilt. +Relais können auf Kanalnachrichten zugreifen. No comment provided by engineer. @@ -10048,6 +10648,11 @@ Relays can access channel messages. Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet. alert message + + Your public address + Ihre öffentliche Adresse + No comment provided by engineer. + Your random profile Ihr Zufallsprofil @@ -10055,10 +10660,12 @@ Relays can access channel messages. Your relay address + Ihre Relais-Adresse No comment provided by engineer. Your relay name + Ihr Relais-Name No comment provided by engineer. @@ -10076,21 +10683,11 @@ Relays can access channel messages. Einstellungen No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Unterstützen Sie uns](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Senden Sie uns eine E-Mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [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. - \_italic_ \_kursiv_ @@ -10108,6 +10705,7 @@ Relays can access channel messages. accepted + Angenommen No comment provided by engineer. @@ -10132,6 +10730,7 @@ Relays can access channel messages. active + Aktiv No comment provided by engineer. @@ -10245,6 +10844,11 @@ marked deleted chat item preview text Anrufen… call status + + can't broadcast + Broadcast nicht möglich + No comment provided by engineer. + can't send messages Es können keine Nachrichten gesendet werden @@ -10282,10 +10886,12 @@ marked deleted chat item preview text channel + Kanal shown as sender role for channel messages channel profile updated + Kanalprofil wurde aktualisiert snd group event chat item @@ -10310,7 +10916,7 @@ marked deleted chat item preview text connecting - verbinde + Verbinde No comment provided by engineer. @@ -10330,7 +10936,7 @@ marked deleted chat item preview text connecting (introduction invitation) - Verbinde (nach einer Einladung) + Verbindung (nach einer Einladung) No comment provided by engineer. @@ -10436,6 +11042,7 @@ pref value deleted channel + Kanal gelöscht rcv group event chat item @@ -10460,7 +11067,7 @@ pref value disabled - deaktiviert + Deaktiviert No comment provided by engineer. @@ -10550,6 +11157,7 @@ pref value error: %@ + Fehler: %@ receive error chat item @@ -10684,6 +11292,7 @@ pref value link + Link No comment provided by engineer. @@ -10703,7 +11312,7 @@ pref value connected - ist der Gruppe beigetreten + Verbunden rcv group event chat item @@ -10758,6 +11367,7 @@ pref value new + Neu No comment provided by engineer. @@ -10885,6 +11495,7 @@ time to disappear relay + Relais member role @@ -10899,8 +11510,14 @@ time to disappear removed (%d attempts) + Entfernt (%d Versuche) receive error chat item + + removed by operator + Vom Betreiber entfernt + No comment provided by engineer. + removed contact address Die Kontaktadresse wurde entfernt @@ -11057,6 +11674,7 @@ Zuletzt empfangene Nachricht: %2$@ updated channel profile + Kanalprofil aktualisiert rcv group event chat item @@ -11081,6 +11699,7 @@ Zuletzt empfangene Nachricht: %2$@ via %@ + via %@ relay hostname @@ -11160,6 +11779,7 @@ Zuletzt empfangene Nachricht: %2$@ you are subscriber + Sie sind Abonnent No comment provided by engineer. @@ -11222,6 +11842,11 @@ Zuletzt empfangene Nachricht: %2$@ \~durchstreichen~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Signaturüberprüfung fehlgeschlagen: %@. + owner verification + @@ -11479,7 +12104,7 @@ Zuletzt empfangene Nachricht: %2$@ Selected chat preferences prohibit this message. - Die gewählten Chat-Einstellungen erlauben diese Nachricht nicht. + Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 88b9939944..5e95cf39cc 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -185,10 +185,23 @@ %d months time interval - - %d relays - %d relays - channel relay bar + + %d relays failed + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -226,11 +239,21 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -240,7 +263,17 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -357,11 +390,21 @@ channel relay bar progress with errors %u messages skipped. No comment provided by engineer. + + (from owner) + (from owner) + chat link info line + (new) (new) No comment provided by engineer. + + (signed) + (signed) + chat link info line + (this device v%@) (this device v%@) @@ -455,6 +498,15 @@ channel relay bar progress with errors - and more! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -553,6 +605,11 @@ time interval A few more things No comment provided by engineer. + + A link for one person to connect + A link for one person to connect + No comment provided by engineer. + A new contact A new contact @@ -679,9 +736,9 @@ swipe action Active connections 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. - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -749,6 +806,11 @@ swipe action Added message servers No comment provided by engineer. + + Adding relays will be supported later. + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Additional accent @@ -869,6 +931,16 @@ swipe action All profiles profile dropdown + + All relays failed + All relays failed + No comment provided by engineer. + + + All relays removed + All relays removed + No comment provided by engineer. + All reports will be archived for you. All reports will be archived for you. @@ -929,6 +1001,11 @@ swipe action Allow irreversible message deletion only if your contact allows it to you. (24 hours) No comment provided by engineer. + + Allow members to chat with admins. + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Allow message reactions only if your contact allows them. @@ -944,6 +1021,11 @@ swipe action Allow sending direct messages to members. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Allow sending disappearing messages. @@ -954,6 +1036,11 @@ swipe action Allow sharing No comment provided by engineer. + + Allow subscribers to chat with admins. + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Allow to irreversibly delete sent messages. (24 hours) @@ -1059,11 +1146,6 @@ swipe action Answer call No comment provided by engineer. - - Anybody can host servers. - Anybody can host servers. - No comment provided by engineer. - App build: %@ App build: %@ @@ -1269,6 +1351,23 @@ swipe action Bad message hash No comment provided by engineer. + + Be free +in your network + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Better calls @@ -1419,6 +1518,11 @@ swipe action Both you and your contact can send voice messages. No comment provided by engineer. + + Bottom bar + Bottom bar + No comment provided by engineer. + Broadcast Broadcast @@ -1432,7 +1536,7 @@ swipe action Business address Business address - No comment provided by engineer. + chat link info line Business chats @@ -1454,15 +1558,6 @@ swipe action By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! Call already ended! @@ -1626,6 +1721,12 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image Channel image @@ -1634,6 +1735,11 @@ set passcode view Channel link Channel link + chat link info line + + + Channel preferences + Channel preferences No comment provided by engineer. @@ -1651,6 +1757,11 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! Channel will be deleted for all subscribers - this cannot be undone! @@ -1666,6 +1777,11 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + Channels + No comment provided by engineer. + Chat Chat @@ -1789,7 +1905,8 @@ set passcode view Chat with admins Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1806,11 +1923,26 @@ set passcode view Chats No comment provided by engineer. + + Chats with admins are prohibited. + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chats with members No comment provided by engineer. + + Chats with members are disabled + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Check messages every 20 min. @@ -1981,11 +2113,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Configure server operators - No comment provided by engineer. - Confirm Confirm @@ -2091,6 +2218,11 @@ This is your own one-time link! Connect via link new chat sheet title + + Connect via link or QR code + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Connect via one-time link @@ -2169,7 +2301,7 @@ This is your own one-time link! Connection error (AUTH) Connection error (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2228,6 +2360,11 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + Contact address + chat link info line + Contact allows Contact allows @@ -2298,6 +2435,11 @@ This is your own one-time link! Continue No comment provided by engineer. + + Contribute + Contribute + No comment provided by engineer. + Conversation deleted! Conversation deleted! @@ -2328,11 +2470,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - Create - No comment provided by engineer. - Create 1-time link Create 1-time link @@ -2403,11 +2540,21 @@ This is your own one-time link! Create your address No comment provided by engineer. + + Create your link + Create your link + No comment provided by engineer. + Create your profile Create your profile No comment provided by engineer. + + Create your public address + Create your public address + No comment provided by engineer. + Created Created @@ -2591,11 +2738,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - Decentralized - No comment provided by engineer. - Decode link Decode link @@ -2998,6 +3140,16 @@ alert button Direct messages between members are prohibited. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + Disable + alert button + Disable (keep overrides) Disable (keep overrides) @@ -3103,6 +3255,11 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Do not use credentials with proxy. @@ -3204,6 +3361,11 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Edit @@ -3227,7 +3389,7 @@ chat item action Enable Enable - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3264,6 +3426,11 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + Enable chats with admins? + alert title + Enable disappearing messages by default. Enable disappearing messages by default. @@ -3284,16 +3451,16 @@ chat item action Enable instant notifications? No comment provided by engineer. + + Enable link previews? + Enable link previews? + alert title + Enable lock Enable lock No comment provided by engineer. - - Enable notifications - Enable notifications - No comment provided by engineer. - Enable periodic notifications? Enable periodic notifications? @@ -3429,6 +3596,11 @@ chat item action Enter password above to show! No comment provided by engineer. + + Enter profile name... + Enter profile name... + No comment provided by engineer. + Enter relay name… Enter relay name… @@ -3462,7 +3634,7 @@ chat item action Error Error - No comment provided by engineer. + conn error description Error aborting address change @@ -3809,6 +3981,11 @@ chat item action Error setting delivery receipts! No comment provided by engineer. + + Error sharing channel + Error sharing channel + alert title + Error starting chat Error starting chat @@ -4177,6 +4354,11 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + For anyone to reach you + No comment provided by engineer. + For chat profile %@: For chat profile %@: @@ -4332,6 +4514,11 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + Get started + No comment provided by engineer. + Good afternoon! Good afternoon! @@ -4390,7 +4577,7 @@ Error: %2$@ Group link Group link - No comment provided by engineer. + chat link info line Group links @@ -4502,6 +4689,11 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works How SimpleX works @@ -4602,11 +4794,6 @@ Error: %2$@ Immediately No comment provided by engineer. - - Immune to spam - Immune to spam - No comment provided by engineer. - Import Import @@ -4749,9 +4936,9 @@ More improvements are coming soon! Initial role No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Install SimpleX Chat for terminal No comment provided by engineer. @@ -4809,7 +4996,7 @@ More improvements are coming soon! Invalid connection link Invalid connection link - No comment provided by engineer. + conn error description Invalid display name! @@ -4876,6 +5063,11 @@ More improvements are coming soon! Invite members No comment provided by engineer. + + Invite someone privately + Invite someone privately + No comment provided by engineer. + Invite to chat Invite to chat @@ -5079,6 +5271,11 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Let's talk in SimpleX Chat @@ -5099,6 +5296,11 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + Link signature verified. + owner verification + Linked desktop options Linked desktop options @@ -5284,6 +5486,11 @@ This is your link for group %@! Members can add message reactions. No comment provided by engineer. + + Members can chat with admins. + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Members can irreversibly delete sent messages. (24 hours) @@ -5449,6 +5656,16 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Messages in this chat will never be deleted. @@ -5479,16 +5696,16 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. + + Migrate + Migrate + No comment provided by engineer. + Migrate device Migrate device No comment provided by engineer. - - Migrate from another device - Migrate from another device - No comment provided by engineer. - Migrate here Migrate here @@ -5609,6 +5826,11 @@ This is your link for group %@! Network & servers No comment provided by engineer. + + Network commitments + Network commitments + No comment provided by engineer. + Network connection Network connection @@ -5619,6 +5841,11 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + Network error + conn error description + Network issues - message expired after many attempts to send it. Network issues - message expired after many attempts to send it. @@ -5634,6 +5861,13 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Network settings @@ -5649,6 +5883,11 @@ This is your link for group %@! New token status text + + New 1-time link + New 1-time link + No comment provided by engineer. + New Passcode New Passcode @@ -5749,6 +5988,18 @@ This is your link for group %@! No No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No active relays + No comment provided by engineer. + No app password No app password @@ -5909,9 +6160,19 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - No user identifiers. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5976,7 +6237,7 @@ This is your link for group %@! OK OK - No comment provided by engineer. + alert button Off @@ -5995,11 +6256,21 @@ new chat action Old database No comment provided by engineer. + + On your phone, not on servers. + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link One-time invitation link No comment provided by engineer. + + One-time link + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -6019,6 +6290,11 @@ Requires compatible VPN. Onion hosts will not be used. No comment provided by engineer. + + Only channel owners can change channel preferences. + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Only chat owners can change preferences. @@ -6122,7 +6398,8 @@ Requires compatible VPN. Open Open - alert action + alert action +alert button Open Settings @@ -6159,6 +6436,11 @@ Requires compatible VPN. Open conditions No comment provided by engineer. + + Open external link? + Open external link? + alert title + Open full link Open full link @@ -6229,6 +6511,17 @@ Requires compatible VPN. Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Or import archive file @@ -6249,6 +6542,11 @@ Requires compatible VPN. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Or show this code @@ -6259,6 +6557,11 @@ Requires compatible VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organize chats into lists @@ -6286,6 +6589,11 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING count @@ -6341,6 +6649,11 @@ Requires compatible VPN. Paste image No comment provided by engineer. + + Paste link / Scan + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Paste link to connect! @@ -6540,14 +6853,14 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Privacy redefined + + Privacy: for owners and subscribers. + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. + Private and secure messaging. No comment provided by engineer. @@ -6620,9 +6933,9 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Profile update will be sent to your contacts. + + Profile update will be sent to your SimpleX contacts. + Profile update will be sent to your SimpleX contacts. alert message @@ -6630,6 +6943,11 @@ Error: %@ Prohibit audio/video calls. No comment provided by engineer. + + Prohibit chats with admins. + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Prohibit irreversible message deletion. @@ -6660,6 +6978,11 @@ Error: %@ Prohibit sending direct messages to members. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Prohibit sending disappearing messages. @@ -6727,6 +7050,11 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push notifications @@ -6767,24 +7095,14 @@ Enable in *Network & servers* settings. Read more No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Read more in User Guide. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Read more in [User Guide](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). - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Read more in our GitHub repository. No comment provided by engineer. @@ -6964,6 +7282,11 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relay server is only used if necessary. Another party can observe your IP address. @@ -6979,6 +7302,11 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + Reliability: many relays per channel. + No comment provided by engineer. + Remove Remove @@ -7264,6 +7592,11 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + Safe web links + No comment provided by engineer. + Safely receive files Safely receive files @@ -7310,6 +7643,11 @@ chat item action Save and notify group members No comment provided by engineer. + + Save and notify subscribers + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Save and reconnect @@ -7510,6 +7848,11 @@ chat item action Security code No comment provided by engineer. + + Security: owners hold channel keys. + Security: owners hold channel keys. + No comment provided by engineer. + Select Select @@ -7640,6 +7983,11 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Send them from gallery or custom keyboards. @@ -7650,6 +7998,11 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Send your private feedback to groups. @@ -7665,6 +8018,11 @@ chat item action Sender may have deleted the connection request. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Sending delivery receipts will be enabled for all contacts in all visible chat profiles. @@ -7925,6 +8283,16 @@ chat item action Settings were changed. alert message + + Setup notifications + Setup notifications + No comment provided by engineer. + + + Setup routers + Setup routers + No comment provided by engineer. + Shape profile images Shape profile images @@ -7961,11 +8329,16 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Share address with contacts? + + Share address with SimpleX contacts? + Share address with SimpleX contacts? alert title + + Share channel + Share channel + No comment provided by engineer. + Share from other apps. Share from other apps. @@ -8006,9 +8379,14 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Share with contacts + + Share via chat + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts + Share with SimpleX contacts No comment provided by engineer. @@ -8259,6 +8637,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + Star on GitHub + No comment provided by engineer. + Start chat Start chat @@ -8364,6 +8747,11 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! Subscriber will be removed from channel - this cannot be undone! @@ -8374,6 +8762,51 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8461,6 +8894,11 @@ Relay address was used to set up this relay for the channel. Take picture No comment provided by engineer. + + Talk to someone + Talk to someone + No comment provided by engineer. + Tap Connect to chat Tap Connect to chat @@ -8476,11 +8914,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel Tap Join channel @@ -8516,6 +8949,11 @@ Relay address was used to set up this relay for the channel. Tap to join incognito No comment provided by engineer. + + Tap to open + Tap to open + No comment provided by engineer. + Tap to paste link Tap to paste link @@ -8619,6 +9057,11 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. The connection reached the limit of undelivered messages, your contact may be offline. @@ -8644,9 +9087,11 @@ It can happen because of some bug or when the connection is compromised.The encryption is working and the new encryption agreement is not required. It may result in connection errors! No comment provided by engineer. - - The future of messaging - The future of messaging + + The first network where you own +your contacts and groups. + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8684,6 +9129,11 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. The same conditions will apply to operator **%@**. @@ -8729,6 +9179,16 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. These conditions will also apply for: **%@**. @@ -8854,6 +9314,11 @@ It can happen because of some bug or when the connection is compromised.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection To make a new connection @@ -8941,11 +9406,6 @@ You will be prompted to complete authentication before this feature is enabled.< To verify end-to-end encryption with your contact compare (or scan) the code on your devices. No comment provided by engineer. - - Toggle chat list: - Toggle chat list: - No comment provided by engineer. - Toggle incognito when connecting. Toggle incognito when connecting. @@ -8961,6 +9421,11 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity No comment provided by engineer. + + Top bar + Top bar + No comment provided by engineer. + Total Total @@ -9131,13 +9596,18 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Update @@ -9263,11 +9733,6 @@ To connect, please ask your contact to create another connection link and check Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - Use chat - No comment provided by engineer. - Use current profile Use current profile @@ -9353,6 +9818,11 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Use web port @@ -9508,6 +9978,11 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Waiting for desktop... @@ -9548,6 +10023,11 @@ To connect, please ask your contact to create another connection link and check Warning: you may lose some data! No comment provided by engineer. + + We made connecting simpler for new users. + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE servers @@ -9598,6 +10078,11 @@ To connect, please ask your contact to create another connection link and check When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. No comment provided by engineer. + + Why SimpleX is built. + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9860,6 +10345,15 @@ Repeat join request? You can't send messages! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. You connected to the channel via this relay link. @@ -9870,11 +10364,6 @@ Repeat join request? You could not be verified; please try again. No comment provided by engineer. - - You decide who can connect. - You decide who can connect. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9942,6 +10431,11 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. You will be able to send messages **only after your request is accepted**. @@ -10082,6 +10576,11 @@ Repeat connection request? Your contacts will remain connected. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Your credentials may be sent unencrypted. @@ -10102,6 +10601,11 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + Your network + No comment provided by engineer. + Your preferences Your preferences @@ -10144,6 +10648,11 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + Your public address + No comment provided by engineer. + Your random profile Your random profile @@ -10174,21 +10683,11 @@ Relays can access channel messages. Your settings No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Send us email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italic_ @@ -10345,6 +10844,11 @@ marked deleted chat item preview text calling… call status + + can't broadcast + can't broadcast + No comment provided by engineer. + can't send messages can't send messages @@ -11009,6 +11513,11 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + removed by operator + No comment provided by engineer. + removed contact address removed contact address @@ -11333,6 +11842,11 @@ last received msg: %2$@ \~strike~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index fe6234e16b..43d3895cc4 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -185,9 +185,23 @@ %d mes(es) time interval - - %d relays - channel relay bar + + %d relays failed + %d servidores han fallado + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d servidores inactivos + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d servidores eliminados + channel relay bar +channel subscriber relay bar %d sec @@ -206,10 +220,12 @@ %d subscriber + %d suscriptor channel subscriber count %d subscribers + %d suscriptores channel subscriber count @@ -219,21 +235,45 @@ %1$d/%2$d relays active + %1$d/%2$d servidores activos channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d servidores activos, %3$d errores + channel relay bar + %1$d/%2$d relays active, %3$d failed + %1$d/%2$d servidores activos, %3$d han fallado channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d servidores activos, %3$d servidores eliminados + channel relay bar %1$d/%2$d relays connected + %1$d/%2$d servidores conectados channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + %1$d/%2$d servidores conectados, %3$d errores + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d servidores conectados, %3$d con fallo + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d servidores conectados, %3$d eliminados + channel subscriber relay bar %lld @@ -247,6 +287,7 @@ channel relay bar progress with errors %lld channel events + %lld eventos del canal No comment provided by engineer. @@ -261,7 +302,7 @@ channel relay bar progress with errors %lld group events - %lld evento(s) de grupo + %lld evento(s) del grupo No comment provided by engineer. @@ -349,11 +390,21 @@ channel relay bar progress with errors %u mensaje(s) omitido(s). No comment provided by engineer. + + (from owner) + (del propietario) + chat link info line + (new) (nuevo) No comment provided by engineer. + + (signed) + (firmado) + chat link info line + (this device v%@) (este dispositivo v%@) @@ -401,6 +452,7 @@ channel relay bar progress with errors **Test relay** to retrieve its name. + **Test servidor** para recibir su nombre. No comment provided by engineer. @@ -446,6 +498,15 @@ channel relay bar progress with errors - ¡y más! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - aceptar el envío de vistas previas de los enlaces. +- prevenir el phishing mediante hipervínculos. +- eliminar el seguimiento de los enlaces. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +605,11 @@ time interval Algunas cosas más No comment provided by engineer. + + A link for one person to connect + Enlace para un solo contacto + No comment provided by engineer. + A new contact Contacto nuevo @@ -670,9 +736,9 @@ swipe action Conexiones activas 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. - Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Añade la dirección a tu perfil para que tus contactos SimpleX puedan compartirla con otros. La actualización del perfil se enviará a tus contactos SimpleX. No comment provided by engineer. @@ -740,6 +806,11 @@ swipe action Servidores de mensajes añadidos No comment provided by engineer. + + Adding relays will be supported later. + Añadir servidores estará disponible en una versión posterior. + No comment provided by engineer. + Additional accent Acento adicional @@ -860,6 +931,16 @@ swipe action Todos los perfiles profile dropdown + + All relays failed + Todos los servidores han fallado + No comment provided by engineer. + + + All relays removed + Todos los servidores eliminados + No comment provided by engineer. + All reports will be archived for you. Todos los informes serán archivados para ti. @@ -920,6 +1001,11 @@ swipe action Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite. (24 horas) No comment provided by engineer. + + Allow members to chat with admins. + Permitir que los miembros chateen con administradores. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Se permiten las reacciones a los mensajes pero sólo si tu contacto también las permite. @@ -935,6 +1021,11 @@ swipe action Se permiten mensajes directos entre miembros. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Se permiten mensajes directos entre suscriptores. + No comment provided by engineer. + Allow sending disappearing messages. Permites el envío de mensajes temporales. @@ -945,6 +1036,11 @@ swipe action Permitir compartir No comment provided by engineer. + + Allow subscribers to chat with admins. + Permitir que los suscriptores chateen con administradores. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Se permite la eliminación irreversible de mensajes. (24 horas) @@ -1050,11 +1146,6 @@ swipe action Responder llamada No comment provided by engineer. - - Anybody can host servers. - Cualquiera puede alojar servidores. - No comment provided by engineer. - App build: %@ Compilación app: %@ @@ -1260,6 +1351,23 @@ swipe action Hash de mensaje incorrecto No comment provided by engineer. + + Be free +in your network + Se libre +en tu red + No comment provided by engineer. + + + Be free in your network. + Se libre en tu red. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Porque hemos destruido el poder de saber quien eres. De manera que tu poder nunca se pueda arrebatar. + No comment provided by engineer. + Better calls Llamadas mejoradas @@ -1357,6 +1465,7 @@ swipe action Block subscriber for all? + ¿Bloquear al suscriptor para todos? No comment provided by engineer. @@ -1409,8 +1518,14 @@ swipe action Tanto tú como tu contacto podéis enviar mensajes de voz. No comment provided by engineer. + + Bottom bar + Barra inferior + No comment provided by engineer. + Broadcast + Emisión compose placeholder for channel owner @@ -1421,7 +1536,7 @@ swipe action Business address Dirección empresarial - No comment provided by engineer. + chat link info line Business chats @@ -1443,15 +1558,6 @@ swipe action Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Al usar SimpleX Chat, aceptas: -- enviar únicamente contenido legal en los grupos públicos. -- respetar a los demás usuarios – spam prohibido. - No comment provided by engineer. - Call already ended! ¡La llamada ha terminado! @@ -1602,48 +1708,80 @@ set passcode view Channel + Canal No comment provided by engineer. Channel display name + Título mostrado del canal No comment provided by engineer. Channel full name (optional) + Título completo del canal (opcional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + El canal no tiene servidores activos. Por favor, intenta unirte más tarde. + alert message +alert subtitle + Channel image + Imagen del canal No comment provided by engineer. Channel link + Enlace del canal + chat link info line + + + Channel preferences + Preferencias del canal No comment provided by engineer. Channel profile + Perfil del canal No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + El perfil del canal se almacena en los dispositivos de los suscriptores y en los servidores de chat. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + El perfil del canal ha sido modificado. Si lo guardas, el perfil actualizado será enviado a los suscriptores. alert message + + Channel temporarily unavailable + Canales no disponibles temporalmente + alert title + Channel will be deleted for all subscribers - this cannot be undone! + El canal será eliminado para todos los suscriptores. ¡No puede deshacerse! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + El canal será eliminado para tí. ¡No puede deshacerse! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + El canal comenzará a funcionar con %1$d de %2$d servidores. ¿Continuar? alert message + + Channels + Canales + No comment provided by engineer. + Chat Chat @@ -1731,18 +1869,22 @@ set passcode view Chat relay + Servidor de chat No comment provided by engineer. Chat relays + Servidores de chat No comment provided by engineer. Chat relays forward messages in channels you create. + Los servidores de chat reenvían los mensajes en los canales que has creado. No comment provided by engineer. Chat relays forward messages to channel subscribers. + Los servidores de chat reenvían los mensajes a los suscriptores del canal. No comment provided by engineer. @@ -1763,7 +1905,8 @@ set passcode view Chat with admins Chatea con administradores - chat toolbar + chat feature +chat toolbar Chat with member @@ -1780,11 +1923,26 @@ set passcode view Chats No comment provided by engineer. + + Chats with admins are prohibited. + Chat con administradores no permitido. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + El chat con administradores en el canal público no dispone de cifrado E2E. Úsalo sólo con servidores de confianza. + alert message + Chats with members Chat con miembros No comment provided by engineer. + + Chats with members are disabled + Chats con miembros desactivado + No comment provided by engineer. + Check messages every 20 min. Comprobar mensajes cada 20 min. @@ -1797,10 +1955,12 @@ set passcode view Check relay address and try again. + Comprueba la dirección del servidor y prueba de nuevo. alert message Check relay name and try again. + Comprueba el nombre del servidor y prueba de nuevo. alert message @@ -1950,11 +2110,7 @@ set passcode view Configure relays - No comment provided by engineer. - - - Configure server operators - Configurar operadores de servidores + Configurar servidores No comment provided by engineer. @@ -2062,6 +2218,11 @@ This is your own one-time link! Conectar mediante enlace new chat sheet title + + Connect via link or QR code + Conecta vía enlace o QR + No comment provided by engineer. + Connect via one-time link Conectar mediante enlace de un sólo uso @@ -2140,10 +2301,11 @@ This is your own one-time link! Connection error (AUTH) Error de conexión (Autenticación) - No comment provided by engineer. + conn error description Connection failed + Conexión fallida No comment provided by engineer. @@ -2198,6 +2360,11 @@ This is your own one-time link! Conexiones No comment provided by engineer. + + Contact address + Dirección de contacto + chat link info line + Contact allows El contacto permite @@ -2268,6 +2435,11 @@ This is your own one-time link! Continuar No comment provided by engineer. + + Contribute + Contribuye + No comment provided by engineer. + Conversation deleted! ¡Conversación eliminada! @@ -2298,11 +2470,6 @@ This is your own one-time link! ¿Corregir el nombre a %@? alert message - - Create - Crear - No comment provided by engineer. - Create 1-time link Crear enlace de un solo uso @@ -2355,10 +2522,12 @@ This is your own one-time link! Create public channel + Crear canal público No comment provided by engineer. Create public channel (BETA) + Crear canal público (BETA) No comment provided by engineer. @@ -2371,11 +2540,21 @@ This is your own one-time link! Crea tu dirección No comment provided by engineer. + + Create your link + Crea tu enlace + No comment provided by engineer. + Create your profile Crea tu perfil No comment provided by engineer. + + Create your public address + Crea tu dirección pública + No comment provided by engineer. + Created Creadas @@ -2398,6 +2577,7 @@ This is your own one-time link! Creating channel + Creando canal No comment provided by engineer. @@ -2558,13 +2738,9 @@ This is your own one-time link! Informe debug No comment provided by engineer. - - Decentralized - Descentralizada - No comment provided by engineer. - Decode link + Decodificar enlace relay test step @@ -2615,10 +2791,12 @@ swipe action Delete channel + Eliminar canal No comment provided by engineer. Delete channel? + ¿Eliminar el canal? No comment provided by engineer. @@ -2794,6 +2972,7 @@ alert button Delete relay + Eliminar servidor No comment provided by engineer. @@ -2961,6 +3140,16 @@ alert button Los mensajes directos entre miembros del grupo no están permitidos. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Los mensajes directos entre suscriptores del canal no están permitidos. + No comment provided by engineer. + + + Disable + Desactivar + alert button + Disable (keep overrides) Desactivar (conservando anulaciones) @@ -2978,7 +3167,7 @@ alert button Disable delete messages - Desactivar + Desactivar eliminar mensajes alert button @@ -3066,6 +3255,11 @@ alert button No se envía el historial a los miembros nuevos. No comment provided by engineer. + + Do not send history to new subscribers. + No se envía el historial a los suscriptores nuevos. + No comment provided by engineer. + Do not use credentials with proxy. No se usan credenciales con proxy. @@ -3167,6 +3361,11 @@ chat item action Notificaciones cifradas E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + Invitar a tus amigos es más fácil 👋 + No comment provided by engineer. + Edit Editar @@ -3174,6 +3373,7 @@ chat item action Edit channel profile + Editar perfil del canal No comment provided by engineer. @@ -3189,7 +3389,7 @@ chat item action Enable Activar - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3213,6 +3413,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Activar al menos un servidor de chat en Servidores y Redes. channel creation warning @@ -3225,6 +3426,11 @@ chat item action Permitir acceso a la cámara No comment provided by engineer. + + Enable chats with admins? + ¿Activar chat con administradores? + alert title + Enable disappearing messages by default. Activa por defecto los mensajes temporales. @@ -3245,16 +3451,16 @@ chat item action ¿Activar notificaciones instantáneas? No comment provided by engineer. + + Enable link previews? + ¿Activar previsualización de enlaces? + alert title + Enable lock Activar bloqueo No comment provided by engineer. - - Enable notifications - Activar notificaciones - No comment provided by engineer. - Enable periodic notifications? ¿Activar notificaciones periódicas? @@ -3362,6 +3568,7 @@ chat item action Enter channel name… + Introduce el título del canal… No comment provided by engineer. @@ -3389,8 +3596,14 @@ chat item action ¡Introduce la contraseña arriba para mostrar! No comment provided by engineer. + + Enter profile name... + Introduce el nombre del perfil… + No comment provided by engineer. + Enter relay name… + Introduce el nombre del servidor… No comment provided by engineer. @@ -3421,7 +3634,7 @@ chat item action Error Error - No comment provided by engineer. + conn error description Error aborting address change @@ -3450,6 +3663,7 @@ chat item action Error adding relay + Error al añadir el servidor alert title @@ -3514,6 +3728,7 @@ chat item action Error creating channel + Error al crear el canal alert title @@ -3698,6 +3913,7 @@ chat item action Error saving channel profile + Error al guardar el perfil del canal No comment provided by engineer. @@ -3765,6 +3981,11 @@ chat item action ¡Error al configurar confirmaciones de entrega! No comment provided by engineer. + + Error sharing channel + Error al compartir el canal + alert title + Error starting chat Error al iniciar Chat @@ -4133,6 +4354,11 @@ server test error Para todos los moderadores No comment provided by engineer. + + For anyone to reach you + Cualquiera puede contactarte + No comment provided by engineer. + For chat profile %@: Para el perfil de chat %@: @@ -4280,6 +4506,7 @@ Error: %2$@ Get link + Recibir el enlace relay test step @@ -4287,6 +4514,11 @@ Error: %2$@ Las menciones ahora se notifican. No comment provided by engineer. + + Get started + Empezar + No comment provided by engineer. + Good afternoon! ¡Buenas tardes! @@ -4345,7 +4577,7 @@ Error: %2$@ Group link Enlace de grupo - No comment provided by engineer. + chat link info line Group links @@ -4457,6 +4689,11 @@ Error: %2$@ El historial no se envía a miembros nuevos. No comment provided by engineer. + + History is not sent to new subscribers. + El historial no se envía a suscriptores nuevos. + No comment provided by engineer. + How SimpleX works Cómo funciona SimpleX @@ -4524,6 +4761,7 @@ Error: %2$@ If you joined or created channels, they will stop working permanently. + Si te has unido o has creado canales, dejarán de funcionar permanentemente. down migration warning @@ -4556,11 +4794,6 @@ Error: %2$@ Inmediatamente No comment provided by engineer. - - Immune to spam - Inmune a spam y abuso - No comment provided by engineer. - Import Importar @@ -4703,9 +4936,9 @@ More improvements are coming soon! Rol inicial No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Instalar terminal para SimpleX Chat No comment provided by engineer. @@ -4763,7 +4996,7 @@ More improvements are coming soon! Invalid connection link Enlace de conexión no válido - No comment provided by engineer. + conn error description Invalid display name! @@ -4787,10 +5020,12 @@ More improvements are coming soon! Invalid relay address! + ¡Dirección de servidor no válido! alert title Invalid relay name! + ¡Nombre de servidor no válido! alert title @@ -4828,6 +5063,11 @@ More improvements are coming soon! Invitar miembros No comment provided by engineer. + + Invite someone privately + Invitación privada + No comment provided by engineer. + Invite to chat Invitar al chat @@ -4906,6 +5146,7 @@ More improvements are coming soon! Join channel + Unirme al canal No comment provided by engineer. @@ -4997,10 +5238,12 @@ This is your link for group %@! Leave channel + Salir del canal No comment provided by engineer. Leave channel? + ¿Salir del canal? No comment provided by engineer. @@ -5028,6 +5271,11 @@ This is your link for group %@! Menos tráfico en redes móviles. No comment provided by engineer. + + Let someone connect to you + Conecta con alguien + No comment provided by engineer. + Let's talk in SimpleX Chat Hablemos en SimpleX Chat @@ -5048,6 +5296,11 @@ This is your link for group %@! ¡Enlazar aplicación móvil con ordenador! 🔗 No comment provided by engineer. + + Link signature verified. + Firma del enlace verificada. + owner verification + Linked desktop options Opciones ordenador enlazado @@ -5233,6 +5486,11 @@ This is your link for group %@! Los miembros pueden añadir reacciones a los mensajes. No comment provided by engineer. + + Members can chat with admins. + Los miembros pueden chatear con los administradores. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) @@ -5300,6 +5558,7 @@ This is your link for group %@! Message error + Mensaje de error No comment provided by engineer. @@ -5397,6 +5656,16 @@ This is your link for group %@! ¡Los mensajes nuevos de %@ serán mostrados! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + Los mensajes en este canal **no están cifrados de extremo a extremo**. Los servidores pueden ver estos mensajes. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + Los mensajes en este canal no están cifrados de extremo a extremo. Los servidores pueden ver estos mensajes. + E2EE info chat item + Messages in this chat will never be deleted. Los mensajes de esta conversación nunca se eliminan. @@ -5427,16 +5696,16 @@ This is your link for group %@! Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo resistente a tecnología cuántica** con secreto perfecto hacía adelante, repudio y recuperación tras ataque. No comment provided by engineer. + + Migrate + Migrar + No comment provided by engineer. + Migrate device Migrar dispositivo No comment provided by engineer. - - Migrate from another device - Migrar desde otro dispositivo - No comment provided by engineer. - Migrate here Migrar aquí @@ -5557,6 +5826,11 @@ This is your link for group %@! Servidores y Redes No comment provided by engineer. + + Network commitments + Compromisos en la red + No comment provided by engineer. + Network connection Conexión de red @@ -5567,6 +5841,11 @@ This is your link for group %@! Descentralización de la red No comment provided by engineer. + + Network error + Error de red + conn error description + Network issues - message expired after many attempts to send it. Problema en la red - el mensaje ha expirado tras muchos intentos de envío. @@ -5582,6 +5861,13 @@ This is your link for group %@! Operador de red No comment provided by engineer. + + Network routers cannot know +who talks to whom + Los routers de la red no pueden saber +quién se comunica con quién + No comment provided by engineer. + Network settings Configuración de red @@ -5597,6 +5883,11 @@ This is your link for group %@! Nuevo token status text + + New 1-time link + Nuevo enlace de 1 solo uso + No comment provided by engineer. + New Passcode Código Nuevo @@ -5624,6 +5915,7 @@ This is your link for group %@! New chat relay + Nuevo servidor de chat No comment provided by engineer. @@ -5696,6 +5988,18 @@ This is your link for group %@! No No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + Sin cuenta. Sin teléfono. Sin email. Sin ID. +El cifrado más seguro. + No comment provided by engineer. + + + No active relays + Sin servidores activos + No comment provided by engineer. + No app password Sin contraseña de la aplicación @@ -5703,10 +6007,12 @@ This is your link for group %@! No chat relays + Sin servidores de chat No comment provided by engineer. No chat relays enabled. + Ningún servidor de chat activado. servers warning @@ -5854,13 +6160,24 @@ This is your link for group %@! Ningún chat sin leer No comment provided by engineer. - - No user identifiers. - Sin identificadores de usuario. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nadie monitorizaba tus conversaciones. Nadie registraba tus ubicaciones. La privacidad nunca fue un lujo, era la manera de vivir. + No comment provided by engineer. + + + Non-profit governance + Gobernanza no lucrativa + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No un candado mejorado en la puerta de otro. No un terrateniente que respeta tu privacidad pero sigue guardando un registro de tus visitantes. Tu no eres el invitado. Estás en tu casa y ningún rey podrá entrar. Tu eres el soberano. No comment provided by engineer. Not all relays connected + Hay servidores no conectados alert title @@ -5920,7 +6237,7 @@ This is your link for group %@! OK OK - No comment provided by engineer. + alert button Off @@ -5939,11 +6256,21 @@ new chat action Base de datos antigua No comment provided by engineer. + + On your phone, not on servers. + En tu teléfono, no en algún servidor. + No comment provided by engineer. + One-time invitation link Enlace de invitación de un solo uso No comment provided by engineer. + + One-time link + Enlace de un solo uso + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5963,6 +6290,11 @@ Requiere activación de la VPN. No se usarán hosts .onion. No comment provided by engineer. + + Only channel owners can change channel preferences. + Sólo los propietarios pueden modificar las preferencias de los canales. + No comment provided by engineer. + Only chat owners can change preferences. Sólo los propietarios del chat pueden cambiar las preferencias. @@ -6066,7 +6398,8 @@ Requiere activación de la VPN. Open Abrir - alert action + alert action +alert button Open Settings @@ -6080,6 +6413,7 @@ Requiere activación de la VPN. Open channel + Abrir canal new chat action @@ -6102,6 +6436,11 @@ Requiere activación de la VPN. Abrir condiciones No comment provided by engineer. + + Open external link? + ¿Abrir enlace externo? + alert title + Open full link Abrir enlace completo @@ -6124,6 +6463,7 @@ Requiere activación de la VPN. Open new channel + Abrir canal nuevo new chat action @@ -6171,6 +6511,17 @@ Requiere activación de la VPN. Servidor del operador alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Los operadores se comprometen a: +- Ser independientes +- Minimizar el tratamiento de metadatos +- Ejecutar código open-source verificado + No comment provided by engineer. + Or import archive file O importa desde un archivo @@ -6191,6 +6542,11 @@ Requiere activación de la VPN. O comparte de forma segura este enlace al archivo No comment provided by engineer. + + Or show QR in person or via video call. + O muestra el código QR en persona o por videollamada. + No comment provided by engineer. + Or show this code O muestra este código @@ -6201,6 +6557,11 @@ Requiere activación de la VPN. O para compartir en privado No comment provided by engineer. + + Or use this QR - print or show online. + O usa el QR, imprímelo o muestralo en línea. + No comment provided by engineer. + Organize chats into lists Organiza tus chats en listas @@ -6220,10 +6581,17 @@ Requiere activación de la VPN. Owner + Propietario No comment provided by engineer. Owners + Propietarios + No comment provided by engineer. + + + Ownership: you can run your own relays. + En propiedad: puedes poner en marcha tus propios servidores. No comment provided by engineer. @@ -6281,6 +6649,11 @@ Requiere activación de la VPN. Pegar imagen No comment provided by engineer. + + Paste link / Scan + Pegar enlace / Escanear + No comment provided by engineer. + Paste link to connect! Pegar enlace para conectar! @@ -6437,10 +6810,12 @@ Error: %@ Preset relay address + Direcciones predefinidas No comment provided by engineer. Preset relay name + Nombres predefinidos No comment provided by engineer. @@ -6478,14 +6853,14 @@ Error: %@ Política de privacidad y condiciones de uso. No comment provided by engineer. - - Privacy redefined - Privacidad redefinida + + Privacy: for owners and subscribers. + Privacidad: para propietarios y suscriptores. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. + + Private and secure messaging. + Mensajería segura y privada. No comment provided by engineer. @@ -6530,6 +6905,7 @@ Error: %@ Proceed + Continuar alert action @@ -6557,9 +6933,9 @@ Error: %@ Tema del perfil No comment provided by engineer. - - Profile update will be sent to your contacts. - La actualización del perfil se enviará a tus contactos. + + Profile update will be sent to your SimpleX contacts. + La actualización del perfil se enviará a tus contactos SimpleX. alert message @@ -6567,6 +6943,11 @@ Error: %@ No se permiten llamadas y videollamadas. No comment provided by engineer. + + Prohibit chats with admins. + El chat con los administradores no está permitido. + No comment provided by engineer. + Prohibit irreversible message deletion. No se permite la eliminación irreversible de mensajes. @@ -6597,6 +6978,11 @@ Error: %@ No se permiten mensajes directos entre miembros. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No se permiten mensajes directos entre suscriptores. + No comment provided by engineer. + Prohibit sending disappearing messages. No se permiten mensajes temporales. @@ -6664,6 +7050,11 @@ Actívalo en ajustes de *Servidores y Redes*. El proxy requiere contraseña No comment provided by engineer. + + Public channels - speak freely 🚀 + Canales públicos - habla con libertad 🚀 + No comment provided by engineer. + Push notifications Notificaciones push @@ -6704,24 +7095,14 @@ Actívalo en ajustes de *Servidores y Redes*. Saber más No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Conoce más en la Guía del Usuario. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Conoce más en el [Manual del Usuario](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). - Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Conoce más en nuestro repositorio GitHub. No comment provided by engineer. @@ -6883,20 +7264,29 @@ swipe action Relay + Servidor No comment provided by engineer. Relay address + Dirección del servidor alert title Relay connection failed + La conexión con el servidor ha fallado alert title Relay link + Enlace servidor No comment provided by engineer. + + Relay results: + Resultados del servidor: + alert message + Relay server is only used if necessary. Another party can observe your IP address. El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP. @@ -6909,6 +7299,12 @@ swipe action Relay test failed! + ¡El test del servidor ha fallado! + No comment provided by engineer. + + + Reliability: many relays per channel. + Fiabilidad: muchos servidores por canal. No comment provided by engineer. @@ -6953,10 +7349,12 @@ swipe action Remove subscriber + Eliminar suscriptor No comment provided by engineer. Remove subscriber? + ¿Eliminar suscriptor? alert title @@ -7194,6 +7592,11 @@ swipe action Proxy SOCKS No comment provided by engineer. + + Safe web links + Enlaces web seguros + No comment provided by engineer. + Safely receive files Recibe archivos de forma segura @@ -7222,6 +7625,7 @@ chat item action Save (and notify subscribers) + Guardar (y notificar suscriptores) alert button @@ -7239,6 +7643,11 @@ chat item action Guardar y notificar grupo No comment provided by engineer. + + Save and notify subscribers + Guardar y notificar suscriptores + No comment provided by engineer. + Save and reconnect Guardar y reconectar @@ -7251,10 +7660,12 @@ chat item action Save channel profile + Guardar perfil del canal No comment provided by engineer. Save channel profile? + ¿Guardar perfil del canal? alert title @@ -7437,6 +7848,11 @@ chat item action Código de seguridad No comment provided by engineer. + + Security: owners hold channel keys. + Seguridad: los propietarios tienen la llave del canal. + No comment provided by engineer. + Select Seleccionar @@ -7567,6 +7983,11 @@ chat item action Enviar solicitud sin mensaje No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Envía el enlace con cualquier mensajero, es seguro. El contacto debe pegarlo en SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Envíalos desde la galería o desde teclados personalizados. @@ -7577,6 +7998,11 @@ chat item action Se envían hasta 100 mensajes más recientes a los miembros nuevos. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Se envían hasta 100 mensajes más recientes a los suscriptores nuevos. + No comment provided by engineer. + Send your private feedback to groups. Envía tu comentario privado a los grupos. @@ -7592,6 +8018,11 @@ chat item action El remitente puede haber eliminado la solicitud de conexión. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Enviar una previsualización del enlace puede revelar tu dirección IP al sitio web. Puedes cambiarlo más tarde en los ajustes de privacidad. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. El envío de confirmaciones de entrega se activará para todos los contactos en todos los perfiles visibles. @@ -7719,6 +8150,7 @@ chat item action Server requires authorization to connect to relay, check password. + El servidor requiere autorización para conectar con el servidor, comprueba la contraseña. relay test error @@ -7851,6 +8283,16 @@ chat item action La configuración ha sido modificada. alert message + + Setup notifications + Configurar notificaciones + No comment provided by engineer. + + + Setup routers + Configurar routers + No comment provided by engineer. + Shape profile images Dar forma a las imágenes de perfil @@ -7887,11 +8329,16 @@ chat item action Campartir dirección públicamente No comment provided by engineer. - - Share address with contacts? - ¿Compartir la dirección con los contactos? + + Share address with SimpleX contacts? + ¿Compartir la dirección con los contactos SimpleX? alert title + + Share channel + Compartir canal + No comment provided by engineer. + Share from other apps. Comparte desde otras aplicaciones. @@ -7919,6 +8366,7 @@ chat item action Share relay address + Compartir dirección del servidor No comment provided by engineer. @@ -7931,9 +8379,14 @@ chat item action Compartir con Simplex No comment provided by engineer. - - Share with contacts - Compartir con contactos + + Share via chat + Compartir mediante chat + No comment provided by engineer. + + + Share with SimpleX contacts + Compartir con contactos SimpleX No comment provided by engineer. @@ -8108,6 +8561,7 @@ chat item action SimpleX relay address + Dirección de servidor SimpleX simplex link type @@ -8183,6 +8637,11 @@ report reason Cuadrada, circular o cualquier forma intermedia. No comment provided by engineer. + + Star on GitHub + Estrella en GitHub + No comment provided by engineer. + Start chat Iniciar chat @@ -8285,19 +8744,74 @@ report reason Subscriber + Suscriptor No comment provided by engineer. + + Subscriber reports + Informes de suscriptores + chat feature + Subscriber will be removed from channel - this cannot be undone! + El suscriptor será eliminado del canal. ¡No puede deshacerse! alert message Subscribers + Suscriptores + No comment provided by engineer. + + + Subscribers can add message reactions. + Los suscriptores pueden añadir reacciones a los mensajes. + No comment provided by engineer. + + + Subscribers can chat with admins. + Los suscriptores pueden chatear con los administradores. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Los suscriptores del canal pueden eliminar mensajes de forma irreversible. (24 horas) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Los suscriptores pueden informar de mensajes a los moderadores. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Los suscriptores del canal pueden enviar enlaces SimpleX. + No comment provided by engineer. + + + Subscribers can send direct messages. + Los suscriptores del canal pueden enviar mensajes directos. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Los suscriptores del canal pueden enviar mensajes temporales. + No comment provided by engineer. + + + Subscribers can send files and media. + Los suscriptores del canal pueden enviar archivos y multimedia. + No comment provided by engineer. + + + Subscribers can send voice messages. + Los suscriptores del canal pueden enviar mensajes de voz. No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Los suscriptores usan el enlace del servidor para conectarse a los canales. +La dirección del servidor se usó para establecer el servidor para el canal. No comment provided by engineer. @@ -8380,6 +8894,11 @@ Relay address was used to set up this relay for the channel. Hacer foto No comment provided by engineer. + + Talk to someone + Para comunicarte + No comment provided by engineer. + Tap Connect to chat Pulsa Conectar para chatear @@ -8395,13 +8914,9 @@ Relay address was used to set up this relay for the channel. Pulsa Conectar para usar el bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Pulsa Crear dirección SimpleX en el menú para crearla más tarde. - No comment provided by engineer. - Tap Join channel + Pulsa Unirme al canal No comment provided by engineer. @@ -8434,6 +8949,11 @@ Relay address was used to set up this relay for the channel. Pulsa para unirte en modo incógnito No comment provided by engineer. + + Tap to open + Pulsa para abrir + No comment provided by engineer. + Tap to paste link Pulsa aquí para pegar el enlace @@ -8462,6 +8982,7 @@ server test failure Test relay + Test servidor No comment provided by engineer. @@ -8518,6 +9039,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The app removed this message after %lld attempts to receive it. + La app ha eliminado el mensaje tras %lld intentos de recibirlo. No comment provided by engineer. @@ -8535,6 +9057,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El código QR escaneado no es un enlace de SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + La conexión ha alcanzado al límite de mensajes no entregados + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado. @@ -8560,9 +9087,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El cifrado funciona y un cifrado nuevo no es necesario. ¡Podría dar lugar a errores de conexión! No comment provided by engineer. - - The future of messaging - La nueva generación de mensajería privada + + The first network where you own +your contacts and groups. + La primera red donde los grupos +y los contactos son tuyos. No comment provided by engineer. @@ -8600,6 +9129,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La base de datos antigua no se eliminó durante la migración, puede eliminarse. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + La libertad más antigua del ser humano, la de hablar con otra persona sin ser observado, materializada sobre una infraestructura que no puede traicionarla. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Las mismas condiciones se aplicarán al operador **%@**. @@ -8645,6 +9179,16 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Temas No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Después pasamos a internet y cada plataforma pedía una parte de tí: tu nombre, tu número, tus amistades. Aceptamos que el precio de hablar con los demás es informar a alguien de quién es interlocutor. Cada generación, personas y tecnología, ha funcionado así: teléfono, email, mensajería, redes sociales. Parecía el único camino. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Existe otro camino. Una red sin números de teléfono. Sin nombres de usuario. Sin cuentas. Sin identificadores de ningún tipo. Una red que conecta las personas y entrega mensajes cifrados sin saber quien está conectado. + No comment provided by engineer. + These conditions will also apply for: **%@**. Estas condiciones también se aplican para: **%@**. @@ -8712,10 +9256,12 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This is a chat relay address, it cannot be used to connect. + Esto es una dirección de servidor, no puede usarse para conectar. alert message This is your link for channel %@! + Este es tu enlace para el canal %@! new chat action @@ -8768,6 +9314,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Para ocultar mensajes no deseados. No comment provided by engineer. + + To make SimpleX Network last. + Para que la Red SimpleX perdure. + No comment provided by engineer. + To make a new connection Para hacer una conexión nueva @@ -8855,11 +9406,6 @@ Se te pedirá que completes la autenticación antes de activar esta función.Para verificar el cifrado de extremo a extremo con tu contacto, compara (o escanea) el código en ambos dispositivos. No comment provided by engineer. - - Toggle chat list: - Alternar lista de chats: - No comment provided by engineer. - Toggle incognito when connecting. Activa incógnito al conectar. @@ -8875,6 +9421,11 @@ Se te pedirá que completes la autenticación antes de activar esta función.Opacidad barra No comment provided by engineer. + + Top bar + Barra superior + No comment provided by engineer. + Total Total @@ -8942,6 +9493,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unblock subscriber for all? + ¿Desbloquear al suscriptor para todos? No comment provided by engineer. @@ -9044,13 +9596,18 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Unsupported connection link Enlace de conexión no compatible - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Hasta 100 últimos mensajes son enviados a los miembros nuevos. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Hasta 100 últimos mensajes son enviados a los suscriptores nuevos. + No comment provided by engineer. + Update Actualizar @@ -9176,11 +9733,6 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar puerto TCP 443 solo en servidores predefinidos. No comment provided by engineer. - - Use chat - Usar Chat - No comment provided by engineer. - Use current profile Usar perfil actual @@ -9198,6 +9750,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use for new channels + Usar para canales nuevos No comment provided by engineer. @@ -9242,6 +9795,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use relay + Usar servidor No comment provided by engineer. @@ -9264,6 +9818,11 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usa la aplicación con una sola mano. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Usa esta dirección en tu perfil de redes sociales, página web o firma email. + No comment provided by engineer. + Use web port Usar puerto web @@ -9286,6 +9845,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Verify + Verificar relay test step @@ -9410,12 +9970,19 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Wait + Espera alert action Wait response + Espera respuesta relay test step + + Waiting for channel owner to add relays. + Esperando a que el propietario del canal añada servidores. + No comment provided by engineer. + Waiting for desktop... Esperando ordenador... @@ -9456,6 +10023,11 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Atención: ¡puedes perder algunos datos! No comment provided by engineer. + + We made connecting simpler for new users. + Hemos simplificado la conexión para los usuarios nuevos. + No comment provided by engineer. + WebRTC ICE servers Servidores WebRTC ICE @@ -9506,6 +10078,11 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. No comment provided by engineer. + + Why SimpleX is built. + Por qué fue creado SimpleX. + No comment provided by engineer. + WiFi WiFi @@ -9720,11 +10297,12 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the channel. + Puedes compartir un enlace o código QR. Cualquiera podrá unirse al canal. No comment provided by engineer. You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. + Puedes compartir el enlace o código QR. Cualquiera podrá unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. No comment provided by engineer. @@ -9767,8 +10345,18 @@ Repeat join request? ¡No puedes enviar mensajes! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + Te comprometes a: +- Sólo contenido legal en grupos públicos +- Respetar a los demás usuarios — no hacer spam + No comment provided by engineer. + You connected to the channel via this relay link. + Te conectaste al canal mediante este enlace de servidor. No comment provided by engineer. @@ -9776,11 +10364,6 @@ Repeat join request? No has podido ser autenticado. Inténtalo de nuevo. No comment provided by engineer. - - You decide who can connect. - Tu decides quién se conecta. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9848,6 +10431,11 @@ Repeat connection request? Deberías recibir notificaciones. token info + + You were born without an account + Naciste sin una cuenta + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Podrás enviar mensajes **después de que tu solicitud sea aceptada**. @@ -9885,11 +10473,12 @@ Repeat connection request? You will stop receiving messages from this channel. Chat history will be preserved. + Dejarás de recibir mensajes de este canal. El historial del chat se conservará. No comment provided by engineer. You will stop receiving messages from this chat. Chat history will be preserved. - Dejarás de recibir mensajes del chat. El historial del chat se conserva. + Dejarás de recibir mensajes del chat. El historial del chat se conservará. No comment provided by engineer. @@ -9934,6 +10523,7 @@ Repeat connection request? Your channel + Tu canal No comment provided by engineer. @@ -9986,6 +10576,11 @@ Repeat connection request? Tus contactos permanecerán conectados. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Tus conversaciones te pertenecen, tal como ha sido siempre antes de la llegada de internet. Tu red no es un lugar que visitas. Es un lugar que has creado, te pertenece y nadie te la podrá quitar, ya sea pública o privada. + No comment provided by engineer. + Your credentials may be sent unencrypted. Tus credenciales podrían ser enviadas sin cifrar. @@ -10006,6 +10601,11 @@ Repeat connection request? Mi grupo No comment provided by engineer. + + Your network + Tu red + No comment provided by engineer. + Your preferences Mis preferencias @@ -10024,6 +10624,8 @@ Repeat connection request? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + El perfil **%@** será compartido con los servidores de canal y los suscriptores. +Los servidores tienen acceso a los mensajes del canal. No comment provided by engineer. @@ -10046,6 +10648,11 @@ Relays can access channel messages. Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos. alert message + + Your public address + Tu dirección pública + No comment provided by engineer. + Your random profile Tu perfil aleatorio @@ -10053,10 +10660,12 @@ Relays can access channel messages. Your relay address + Tu dirección de servidor No comment provided by engineer. Your relay name + Tu nombre del servidor No comment provided by engineer. @@ -10074,21 +10683,11 @@ Relays can access channel messages. Configuración No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribuye](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Contacta vía email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Estrella en GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italic_ @@ -10106,6 +10705,7 @@ Relays can access channel messages. accepted + aceptado No comment provided by engineer. @@ -10130,6 +10730,7 @@ Relays can access channel messages. active + activo No comment provided by engineer. @@ -10243,6 +10844,11 @@ marked deleted chat item preview text llamando… call status + + can't broadcast + no puedes retransmitir + No comment provided by engineer. + can't send messages no se pueden enviar mensajes @@ -10280,10 +10886,12 @@ marked deleted chat item preview text channel + canal shown as sender role for channel messages channel profile updated + perfil del canal actualizado snd group event chat item @@ -10434,6 +11042,7 @@ pref value deleted channel + canal eliminado rcv group event chat item @@ -10548,6 +11157,7 @@ pref value error: %@ + error: %@ receive error chat item @@ -10557,6 +11167,7 @@ pref value failed + fallo No comment provided by engineer. @@ -10681,6 +11292,7 @@ pref value link + enlace No comment provided by engineer. @@ -10755,6 +11367,7 @@ pref value new + nuevo No comment provided by engineer. @@ -10882,6 +11495,7 @@ time to disappear relay + servidor member role @@ -10896,8 +11510,14 @@ time to disappear removed (%d attempts) + eliminado (%d intentos) receive error chat item + + removed by operator + eliminado por el operador + No comment provided by engineer. + removed contact address dirección de contacto eliminada @@ -11054,6 +11674,7 @@ last received msg: %2$@ updated channel profile + perfil del canal actualizado rcv group event chat item @@ -11078,6 +11699,7 @@ last received msg: %2$@ via %@ + mediante %@ relay hostname @@ -11157,6 +11779,7 @@ last received msg: %2$@ you are subscriber + eres suscriptor No comment provided by engineer. @@ -11219,6 +11842,11 @@ last received msg: %2$@ \~strike~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Verificación de firma fallida: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 89bea61cee..892f686bd2 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -172,9 +172,20 @@ %d kuukautta time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -208,10 +219,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -219,7 +238,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -330,10 +357,18 @@ channel relay bar progress with errors %u viestit ohitettu. No comment provided by engineer. + + (from owner) + chat link info line + (new) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) No comment provided by engineer. @@ -417,6 +452,12 @@ channel relay bar progress with errors - ja paljon muuta! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -508,6 +549,10 @@ time interval Muutama asia lisää No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Uusi kontakti @@ -622,9 +667,8 @@ swipe action Active connections 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. - Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -684,6 +728,10 @@ swipe action Added message servers No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -789,6 +837,14 @@ swipe action All profiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -843,6 +899,10 @@ swipe action Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle. (24 tuntia) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Salli reaktiot viesteihin vain, jos kontaktisi sallii ne. @@ -858,6 +918,10 @@ swipe action Salli yksityisviestien lähettäminen jäsenille. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Salli katoavien viestien lähettäminen. @@ -867,6 +931,10 @@ swipe action Allow sharing No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia) @@ -965,11 +1033,6 @@ swipe action Vastaa puheluun No comment provided by engineer. - - Anybody can host servers. - Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. - No comment provided by engineer. - App build: %@ Sovellusversio: %@ @@ -1156,6 +1219,19 @@ swipe action Virheellinen viestin tarkiste No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls No comment provided by engineer. @@ -1282,6 +1358,10 @@ swipe action Sekä sinä että kontaktisi voitte lähettää ääniviestejä. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1292,7 +1372,7 @@ swipe action Business address - No comment provided by engineer. + chat link info line Business chats @@ -1311,12 +1391,6 @@ swipe action Chat-profiilin mukaan (oletus) tai [yhteyden mukaan](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! Puhelu on jo päättynyt! @@ -1465,12 +1539,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1485,6 +1568,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1497,6 +1584,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat No comment provided by engineer. @@ -1603,7 +1694,8 @@ set passcode view Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1618,10 +1710,22 @@ set passcode view Keskustelut No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1769,10 +1873,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm Vahvista @@ -1863,6 +1963,10 @@ This is your own one-time link! Yhdistä linkin kautta new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Yhdistä kertalinkillä @@ -1931,7 +2035,7 @@ This is your own one-time link! Connection error (AUTH) Yhteysvirhe (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -1980,6 +2084,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kontakti sallii @@ -2045,6 +2153,11 @@ This is your own one-time link! Jatka No comment provided by engineer. + + Contribute + Osallistu + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2071,11 +2184,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - Luo - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2139,11 +2247,19 @@ This is your own one-time link! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Luo profiilisi No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2315,11 +2431,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - Hajautettu - No comment provided by engineer. - Decode link relay test step @@ -2687,6 +2798,14 @@ alert button Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Poista käytöstä (pidä ohitukset) @@ -2784,6 +2903,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -2872,6 +2995,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Muokkaa @@ -2893,7 +3020,7 @@ chat item action Enable Salli - No comment provided by engineer. + alert button Enable (keep overrides) @@ -2927,6 +3054,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -2945,16 +3076,15 @@ chat item action Salli välittömät ilmoitukset? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Ota lukitus käyttöön No comment provided by engineer. - - Enable notifications - Salli ilmoitukset - No comment provided by engineer. - Enable periodic notifications? Salli säännölliset ilmoitukset? @@ -3080,6 +3210,10 @@ chat item action Kirjoita yllä oleva salasana näyttääksesi! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3110,7 +3244,7 @@ chat item action Error Virhe - No comment provided by engineer. + conn error description Error aborting address change @@ -3422,6 +3556,10 @@ chat item action Virhe toimituskuittauksien asettamisessa! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Virhe käynnistettäessä keskustelua @@ -3755,6 +3893,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -3885,6 +4027,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -3939,7 +4085,7 @@ Error: %2$@ Group link Ryhmälinkki - No comment provided by engineer. + chat link info line Group links @@ -4047,6 +4193,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Miten SimpleX toimii @@ -4140,11 +4290,6 @@ Error: %2$@ Heti No comment provided by engineer. - - Immune to spam - Immuuni roskapostille ja väärinkäytöksille - No comment provided by engineer. - Import Tuo @@ -4275,9 +4420,9 @@ More improvements are coming soon! Alkuperäinen rooli No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Asenna SimpleX Chat terminaalille No comment provided by engineer. @@ -4328,7 +4473,7 @@ More improvements are coming soon! Invalid connection link Virheellinen yhteyslinkki - No comment provided by engineer. + conn error description Invalid display name! @@ -4387,6 +4532,10 @@ More improvements are coming soon! Kutsu jäseniä No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4574,6 +4723,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Jutellaan SimpleX Chatissa @@ -4593,6 +4746,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -4760,6 +4917,10 @@ This is your link for group %@! Ryhmän jäsenet voivat lisätä viestireaktioita. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia) @@ -4906,6 +5067,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -4930,12 +5099,12 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device + + Migrate device No comment provided by engineer. @@ -5049,6 +5218,10 @@ This is your link for group %@! Verkko ja palvelimet No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection No comment provided by engineer. @@ -5057,6 +5230,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5069,6 +5246,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Verkkoasetukset @@ -5083,6 +5265,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Uusi pääsykoodi @@ -5172,6 +5358,15 @@ This is your link for group %@! Ei No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Ei sovelluksen salasanaa @@ -5311,9 +5506,16 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5369,7 +5571,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5388,11 +5590,19 @@ new chat action Vanha tietokanta No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Kertakutsulinkki No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5412,6 +5622,10 @@ Edellyttää VPN:n sallimista. Onion-isäntiä ei käytetä. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5508,7 +5722,8 @@ Edellyttää VPN:n sallimista. Open - alert action + alert action +alert button Open Settings @@ -5541,6 +5756,10 @@ Edellyttää VPN:n sallimista. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -5597,6 +5816,13 @@ Edellyttää VPN:n sallimista. Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -5613,6 +5839,10 @@ Edellyttää VPN:n sallimista. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5621,6 +5851,10 @@ Edellyttää VPN:n sallimista. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5642,6 +5876,10 @@ Edellyttää VPN:n sallimista. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING-määrä @@ -5695,6 +5933,10 @@ Edellyttää VPN:n sallimista. Liitä kuva No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5872,13 +6114,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Yksityisyys uudelleen määritettynä + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -5941,9 +6182,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Profiilipäivitys lähetetään kontakteillesi. + + Profile update will be sent to your SimpleX contacts. alert message @@ -5951,6 +6191,10 @@ Error: %@ Estä ääni- ja videopuhelut. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Estä peruuttamaton viestien poistaminen. @@ -5979,6 +6223,10 @@ Error: %@ Estä suorien viestien lähettäminen jäsenille. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Estä katoavien viestien lähettäminen. @@ -6039,6 +6287,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push-ilmoitukset @@ -6076,23 +6328,14 @@ Enable in *Network & servers* settings. Lue lisää No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Lue lisää Käyttöoppaasta. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Lue lisää [Käyttöoppaasta](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). - Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Lue lisää GitHub-arkistosta. No comment provided by engineer. @@ -6253,6 +6496,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Välityspalvelinta käytetään vain tarvittaessa. Toinen osapuoli voi tarkkailla IP-osoitettasi. @@ -6267,6 +6514,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Poista @@ -6519,6 +6770,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6560,6 +6815,10 @@ chat item action Tallenna ja ilmoita ryhmän jäsenille No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6738,6 +6997,10 @@ chat item action Turvakoodi No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Valitse @@ -6856,6 +7119,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Lähetä ne galleriasta tai mukautetuista näppäimistöistä. @@ -6865,6 +7132,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -6879,6 +7150,10 @@ chat item action Lähettäjä on saattanut poistaa yhteyspyynnön. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Toimituskuittauksien lähettäminen otetaan käyttöön kaikille kontakteille näkyvissä keskusteluprofiileissa. @@ -7112,6 +7387,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -7144,11 +7427,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Jaa osoite kontakteille? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7182,9 +7468,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Jaa kontaktien kanssa + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7407,6 +7696,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + Tähti GitHubissa + No comment provided by engineer. + Start chat Aloita keskustelu @@ -7503,6 +7797,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7511,6 +7809,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -7588,6 +7922,10 @@ Relay address was used to set up this relay for the channel. Ota kuva No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7600,10 +7938,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7636,6 +7970,10 @@ Relay address was used to set up this relay for the channel. Napauta liittyäksesi incognito-tilassa No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7729,6 +8067,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -7753,9 +8095,9 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin! No comment provided by engineer. - - The future of messaging - Seuraavan sukupolven yksityisviestit + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -7790,6 +8132,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -7829,6 +8175,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -7939,6 +8293,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Uuden yhteyden luominen @@ -8017,10 +8375,6 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Voit tarkistaa päästä päähän -salauksen kontaktisi kanssa vertaamalla (tai skannaamalla) laitteidenne koodia. No comment provided by engineer. - - Toggle chat list: - No comment provided by engineer. - Toggle incognito when connecting. No comment provided by engineer. @@ -8033,6 +8387,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Toolbar opacity No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total No comment provided by engineer. @@ -8188,12 +8546,16 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Päivitä @@ -8302,11 +8664,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - Käytä chattia - No comment provided by engineer. - Use current profile Käytä nykyistä profiilia @@ -8380,6 +8737,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8518,6 +8879,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8554,6 +8919,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Varoitus: saatat menettää joitain tietoja! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE -palvelimet @@ -8600,6 +8969,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Kun jaat inkognitoprofiilin jonkun kanssa, tätä profiilia käytetään ryhmissä, joihin tämä sinut kutsuu. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -8830,6 +9203,12 @@ Repeat join request? Et voi lähettää viestejä! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -8839,11 +9218,6 @@ Repeat join request? Sinua ei voitu todentaa; yritä uudelleen. No comment provided by engineer. - - You decide who can connect. - Kimin bağlanabileceğine siz karar verirsiniz. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -8905,6 +9279,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9035,6 +9413,10 @@ Repeat connection request? Kontaktisi pysyvät yhdistettyinä. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9053,6 +9435,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Asetuksesi @@ -9091,6 +9477,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Satunnainen profiilisi @@ -9118,21 +9508,11 @@ Relays can access channel messages. Asetuksesi No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Osallistu](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Lähetä meille sähköpostia](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italic_ @@ -9273,6 +9653,10 @@ marked deleted chat item preview text soittaa… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -9899,6 +10283,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10189,6 +10577,10 @@ last received msg: %2$@ \~strike~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 5fcada4ccf..be6a766ca1 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -185,9 +185,20 @@ %d mois time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u messages sautés. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nouveau) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (cet appareil v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - et bien d'autres choses encore ! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Encore quelques points No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Un nouveau contact @@ -670,9 +715,8 @@ swipe action Connections actives 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. - Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Ajout de serveurs de messages No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Accent additionnel @@ -859,6 +907,14 @@ swipe action Tous les profiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Tous les rapports seront archivés pour vous. @@ -919,6 +975,10 @@ swipe action Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Autoriser les réactions aux messages uniquement si votre contact les autorise. @@ -934,6 +994,10 @@ swipe action Autoriser l'envoi de messages directs aux membres. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Autorise l’envoi de messages éphémères. @@ -944,6 +1008,10 @@ swipe action Autoriser le partage No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Autoriser la suppression irréversible de messages envoyés. (24 heures) @@ -1049,11 +1117,6 @@ swipe action Répondre à l'appel No comment provided by engineer. - - Anybody can host servers. - N'importe qui peut heberger un serveur. - No comment provided by engineer. - App build: %@ Build de l'app : %@ @@ -1257,6 +1320,19 @@ swipe action Mauvais hash de message No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Appels améliorés @@ -1402,6 +1478,10 @@ swipe action Vous et votre contact êtes tous deux en mesure d'envoyer des messages vocaux. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1414,7 +1494,7 @@ swipe action Business address Adresse professionnelle - No comment provided by engineer. + chat link info line Business chats @@ -1435,15 +1515,6 @@ swipe action Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - En utilisant SimpleX Chat, vous acceptez de : -- n'envoyer que du contenu légal dans les groupes publics. -- respecter les autres utilisateurs - pas de spam. - No comment provided by engineer. - Call already ended! Appel déjà terminé ! @@ -1603,12 +1674,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1623,6 +1703,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1635,6 +1719,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Discussions @@ -1753,7 +1841,8 @@ set passcode view Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1768,10 +1857,22 @@ set passcode view Discussions No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Consulter les messages toutes les 20 minutes. @@ -1939,11 +2040,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Configurer les opérateurs de serveur - No comment provided by engineer. - Confirm Confirmer @@ -2048,6 +2144,10 @@ Il s'agit de votre propre lien unique ! Se connecter via un lien new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Se connecter via un lien unique @@ -2126,7 +2226,7 @@ Il s'agit de votre propre lien unique ! Connection error (AUTH) Erreur de connexion (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2184,6 +2284,10 @@ Il s'agit de votre propre lien unique ! Connexions No comment provided by engineer. + + Contact address + chat link info line + Contact allows Votre contact autorise @@ -2253,6 +2357,11 @@ Il s'agit de votre propre lien unique ! Continuer No comment provided by engineer. + + Contribute + Contribuer + No comment provided by engineer. + Conversation deleted! Conversation supprimée ! @@ -2283,11 +2392,6 @@ Il s'agit de votre propre lien unique ! Corriger le nom pour %@ ? alert message - - Create - Créer - No comment provided by engineer. - Create 1-time link Créer un lien unique @@ -2355,11 +2459,19 @@ Il s'agit de votre propre lien unique ! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Créez votre profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Créées @@ -2542,11 +2654,6 @@ Il s'agit de votre propre lien unique ! Livraison de débogage No comment provided by engineer. - - Decentralized - Décentralisé - No comment provided by engineer. - Decode link relay test step @@ -2940,6 +3047,14 @@ alert button Les messages directs entre membres sont interdits dans ce groupe. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Désactiver (conserver les remplacements) @@ -3045,6 +3160,10 @@ alert button Ne pas envoyer d'historique aux nouveaux membres. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Ne pas utiliser d'identifiants avec le proxy. @@ -3146,6 +3265,10 @@ chat item action Notifications chiffrées E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Modifier @@ -3167,7 +3290,7 @@ chat item action Enable Activer - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3203,6 +3326,10 @@ chat item action Autoriser l'accès à la caméra No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -3222,16 +3349,15 @@ chat item action Activer les notifications instantanées ? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Activer le verrouillage No comment provided by engineer. - - Enable notifications - Activer les notifications - No comment provided by engineer. - Enable periodic notifications? Activer les notifications périodiques ? @@ -3366,6 +3492,10 @@ chat item action Entrez ci-dessus le mot de passe pour afficher le profil ! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3398,7 +3528,7 @@ chat item action Error Erreur - No comment provided by engineer. + conn error description Error aborting address change @@ -3735,6 +3865,10 @@ chat item action Erreur lors de la configuration des accusés de réception ! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Erreur lors du démarrage du chat @@ -4096,6 +4230,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Pour le profil de discussion %@ : @@ -4248,6 +4386,10 @@ Erreur : %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Bonjour ! @@ -4306,7 +4448,7 @@ Erreur : %2$@ Group link Lien du groupe - No comment provided by engineer. + chat link info line Group links @@ -4415,6 +4557,10 @@ Erreur : %2$@ L'historique n'est pas envoyé aux nouveaux membres. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Comment SimpleX fonctionne @@ -4512,11 +4658,6 @@ Erreur : %2$@ Immédiatement No comment provided by engineer. - - Immune to spam - Protégé du spam et des abus - No comment provided by engineer. - Import Importer @@ -4657,9 +4798,9 @@ D'autres améliorations sont à venir ! Rôle initial No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installer SimpleX Chat pour terminal No comment provided by engineer. @@ -4712,7 +4853,7 @@ D'autres améliorations sont à venir ! Invalid connection link Lien de connection invalide - No comment provided by engineer. + conn error description Invalid display name! @@ -4776,6 +4917,10 @@ D'autres améliorations sont à venir ! Inviter des membres No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Inviter à discuter @@ -4974,6 +5119,10 @@ Voici votre lien pour le groupe %@ ! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Discutons sur SimpleX Chat @@ -4994,6 +5143,10 @@ Voici votre lien pour le groupe %@ ! Liez vos applications mobiles et de bureau ! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Options de bureau lié @@ -5168,6 +5321,10 @@ Voici votre lien pour le groupe %@ ! Les membres du groupe peuvent ajouter des réactions aux messages. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures) @@ -5328,6 +5485,14 @@ Voici votre lien pour le groupe %@ ! Les messages de %@ seront affichés ! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5357,16 +5522,15 @@ Voici votre lien pour le groupe %@ ! Les messages, fichiers et appels sont protégés par un chiffrement **e2e résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Transférer l'appareil No comment provided by engineer. - - Migrate from another device - Transférer depuis un autre appareil - No comment provided by engineer. - Migrate here Transférer ici @@ -5485,6 +5649,10 @@ Voici votre lien pour le groupe %@ ! Réseau et serveurs No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection Connexion au réseau @@ -5495,6 +5663,10 @@ Voici votre lien pour le groupe %@ ! Décentralisation du réseau No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Problèmes de réseau - le message a expiré après plusieurs tentatives d'envoi. @@ -5510,6 +5682,11 @@ Voici votre lien pour le groupe %@ ! Opérateur de réseau No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Paramètres réseau @@ -5524,6 +5701,10 @@ Voici votre lien pour le groupe %@ ! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nouveau code d'accès @@ -5621,6 +5802,15 @@ Voici votre lien pour le groupe %@ ! Non No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Pas de mot de passe pour l'app @@ -5771,9 +5961,16 @@ Voici votre lien pour le groupe %@ ! No unread chats No comment provided by engineer. - - No user identifiers. - Aucun identifiant d'utilisateur. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5834,7 +6031,7 @@ Voici votre lien pour le groupe %@ ! OK OK - No comment provided by engineer. + alert button Off @@ -5853,11 +6050,19 @@ new chat action Ancienne base de données No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Lien d'invitation unique No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5877,6 +6082,10 @@ Nécessite l'activation d'un VPN. Les hôtes .onion ne seront pas utilisés. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Seuls les propriétaires peuvent modifier les préférences. @@ -5976,7 +6185,8 @@ Nécessite l'activation d'un VPN. Open Ouvrir - alert action + alert action +alert button Open Settings @@ -6011,6 +6221,10 @@ Nécessite l'activation d'un VPN. Ouvrir les conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6072,6 +6286,13 @@ Nécessite l'activation d'un VPN. Serveur de l'opérateur alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Ou importer un fichier d'archive @@ -6092,6 +6313,10 @@ Nécessite l'activation d'un VPN. Ou partagez en toute sécurité le lien de ce fichier No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Ou montrez ce code @@ -6102,6 +6327,10 @@ Nécessite l'activation d'un VPN. Ou à partager en privé No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -6126,6 +6355,10 @@ Nécessite l'activation d'un VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Nombre de PING @@ -6181,6 +6414,10 @@ Nécessite l'activation d'un VPN. Coller l'image No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Collez le lien pour vous connecter ! @@ -6373,13 +6610,12 @@ Erreur : %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - La vie privée redéfinie + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -6449,9 +6685,8 @@ Erreur : %@ Thème de profil No comment provided by engineer. - - Profile update will be sent to your contacts. - La mise à jour du profil sera envoyée à vos contacts. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6459,6 +6694,10 @@ Erreur : %@ Interdire les appels audio/vidéo. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Interdire la suppression irréversible des messages. @@ -6488,6 +6727,10 @@ Erreur : %@ Interdire l'envoi de messages directs aux membres. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Interdire l’envoi de messages éphémères. @@ -6554,6 +6797,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Le proxy est protégé par un mot de passe No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Notifications push @@ -6594,24 +6841,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. En savoir plus No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Pour en savoir plus, consultez le Guide de l'utilisateur. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Pour en savoir plus, consultez le [Guide de l'utilisateur](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). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Pour en savoir plus, consultez notre dépôt GitHub. No comment provided by engineer. @@ -6783,6 +7020,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Le serveur relais n'est utilisé que si nécessaire. Un tiers peut observer votre adresse IP. @@ -6797,6 +7038,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Supprimer @@ -7063,6 +7308,10 @@ swipe action proxy SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Réception de fichiers en toute sécurité @@ -7106,6 +7355,10 @@ chat item action Enregistrer et en informer les membres du groupe No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Sauvegarder et se reconnecter @@ -7297,6 +7550,10 @@ chat item action Code de sécurité No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Choisir @@ -7423,6 +7680,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Envoyez-les depuis la phototèque ou des claviers personnalisés. @@ -7433,6 +7694,10 @@ chat item action Envoi des 100 derniers messages aux nouveaux membres. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7447,6 +7712,10 @@ chat item action L'expéditeur a peut-être supprimé la demande de connexion. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. L'envoi d'accusés de réception sera activé pour tous les contacts dans tous les profils de chat visibles. @@ -7702,6 +7971,14 @@ chat item action Les paramètres ont été modifiés. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Images de profil modelable @@ -7738,11 +8015,14 @@ chat item action Partager publiquement votre adresse No comment provided by engineer. - - Share address with contacts? - Partager l'adresse avec vos contacts ? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Partager depuis d'autres applications. @@ -7780,9 +8060,12 @@ chat item action Partager sur SimpleX No comment provided by engineer. - - Share with contacts - Partager avec vos contacts + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8026,6 +8309,11 @@ report reason Carré, circulaire, ou toute autre forme intermédiaire. No comment provided by engineer. + + Star on GitHub + Star sur GitHub + No comment provided by engineer. + Start chat Démarrer le chat @@ -8129,6 +8417,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8137,6 +8429,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8220,6 +8548,10 @@ Relay address was used to set up this relay for the channel. Prendre une photo No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -8232,11 +8564,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Appuyez sur Créer une adresse SimpleX dans le menu pour la créer ultérieurement. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8270,6 +8597,10 @@ Relay address was used to set up this relay for the channel. Appuyez pour rejoindre incognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Appuyez pour coller le lien @@ -8369,6 +8700,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le code scanné n'est pas un code QR de lien SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. La connexion a atteint la limite des messages non délivrés, votre contact est peut-être hors ligne. @@ -8394,9 +8729,9 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion ! No comment provided by engineer. - - The future of messaging - La nouvelle génération de messagerie privée + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8433,6 +8768,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Les mêmes conditions s'appliquent à l'opérateur **%@**. @@ -8478,6 +8817,14 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Thèmes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Ces conditions s'appliquent également aux : **%@**. @@ -8596,6 +8943,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Pour cacher les messages indésirables. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Pour établir une nouvelle connexion @@ -8681,11 +9032,6 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils. No comment provided by engineer. - - Toggle chat list: - Afficher la liste des conversations : - No comment provided by engineer. - Toggle incognito when connecting. Basculer en mode incognito lors de la connexion. @@ -8700,6 +9046,10 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Opacité de la barre d'outils No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total Total @@ -8867,13 +9217,17 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Les 100 derniers messages sont envoyés aux nouveaux membres. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Mise à jour @@ -8990,11 +9344,6 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - Utiliser le chat - No comment provided by engineer. - Use current profile Utiliser le profil actuel @@ -9077,6 +9426,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser l'application d'une main. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -9227,6 +9580,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... En attente du bureau... @@ -9267,6 +9624,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Attention : vous risquez de perdre des données ! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Serveurs WebRTC ICE @@ -9316,6 +9677,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9574,6 +9939,12 @@ Répéter la demande d'adhésion ? Vous ne pouvez pas envoyer de messages ! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9583,11 +9954,6 @@ Répéter la demande d'adhésion ? Vous n'avez pas pu être vérifié·e ; veuillez réessayer. No comment provided by engineer. - - You decide who can connect. - Vous choisissez qui peut se connecter. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9654,6 +10020,10 @@ Répéter la demande de connexion ? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9788,6 +10158,10 @@ Répéter la demande de connexion ? Vos contacts resteront connectés. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Vos informations d'identification peuvent être envoyées non chiffrées. @@ -9807,6 +10181,10 @@ Répéter la demande de connexion ? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Vos préférences @@ -9847,6 +10225,10 @@ Relays can access channel messages. Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Votre profil aléatoire @@ -9875,21 +10257,11 @@ Relays can access channel messages. Vos paramètres No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribuer](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Contact par mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Star sur GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italique_ @@ -10040,6 +10412,10 @@ marked deleted chat item preview text appel… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -10680,6 +11056,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address suppression de l'adresse de contact @@ -10993,6 +11373,10 @@ dernier message reçu : %2$@ \~barré~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 4837804697..7723cabdcb 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -185,9 +185,23 @@ %d hónap time interval - - %d relays - channel relay bar + + %d relays failed + %d átjátszóhoz nem sikerült kapcsolódni + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d átjátszó inaktív + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d átjátszó eltávolítva + channel relay bar +channel subscriber relay bar %d sec @@ -206,10 +220,12 @@ %d subscriber + %d feliratkozó channel subscriber count %d subscribers + %d feliratkozó channel subscriber count @@ -219,21 +235,45 @@ %1$d/%2$d relays active + %1$d/%2$d átjátszó aktív channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d átjátszó aktív, %3$d hiba + channel relay bar + %1$d/%2$d relays active, %3$d failed + %1$d/%2$d átjátszó aktív, %3$d sikertelen channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d átjátszó aktív, %3$d eltávolítva + channel relay bar %1$d/%2$d relays connected + %1$d/%2$d átjátszó kapcsolódva channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + %1$d/%2$d átjátszó kapcsolódva, %3$d hiba + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d átjátszó kapcsolódott, %3$d átjátszóhoz nem sikerült kapcsolódni + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d átjátszó kapcsolódott, %3$d eltávolítva + channel subscriber relay bar %lld @@ -247,6 +287,7 @@ channel relay bar progress with errors %lld channel events + %lld csatornaesemény No comment provided by engineer. @@ -349,11 +390,21 @@ channel relay bar progress with errors %u üzenet kihagyva. No comment provided by engineer. + + (from owner) + (a tulajdonostól) + chat link info line + (new) (új) No comment provided by engineer. + + (signed) + (aláírva) + chat link info line + (this device v%@) (ez az eszköz: v%@) @@ -376,7 +427,7 @@ channel relay bar progress with errors **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. - **Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). + **A legprivátabb**: Az alkalmazás nem használja a SimpleX Chat push-kiszolgálóját. Az alkalmazás a háttérben ellenőrzi az üzeneteket, amikor a rendszer ezt lehetővé teszi, attól függően, hogy Ön milyen gyakran használja az alkalmazást. No comment provided by engineer. @@ -386,7 +437,7 @@ channel relay bar progress with errors **Please note**: you will NOT be able to recover or change passphrase if you lose it. - **Megjegyzés:** NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti. + **Megjegyzés:** NEM fogja tudni helyreállítani vagy módosítani a jelmondatot abban az esetben, ha elveszíti. No comment provided by engineer. @@ -401,6 +452,7 @@ channel relay bar progress with errors **Test relay** to retrieve its name. + **Átjátszó tesztelése** a nevének lekéréséhez. No comment provided by engineer. @@ -446,6 +498,15 @@ channel relay bar progress with errors - és még sok más! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - Hivatkozások előnézetének küldése. +- Hiperhivatkozásokon keresztüli adathalászat megakadályozása. +- Hivatkozások nyomonkövetési paramétereinek eltávolítása. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -514,7 +575,7 @@ time interval 1-time link can be used *with one contact only* - share in person or via any messenger. - Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható. + Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó alkalmazáson keresztül megosztható. No comment provided by engineer. @@ -544,6 +605,11 @@ time interval Néhány további dolog No comment provided by engineer. + + A link for one person to connect + Egy hivatkozás, ami egyetlen partnerrel való kapcsolat létrehozására szolgál + No comment provided by engineer. + A new contact Egy új partner @@ -670,9 +736,9 @@ swipe action Aktív kapcsolatok száma 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. - Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Cím hozzáadása a profilhoz, hogy a SimpleX partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve a SimpleX partnerei számára. No comment provided by engineer. @@ -740,6 +806,11 @@ swipe action Hozzáadott üzenetkiszolgálók No comment provided by engineer. + + Adding relays will be supported later. + Az átjátszók hozzáadása később lesz támogatott. + No comment provided by engineer. + Additional accent További kiemelőszín @@ -860,6 +931,16 @@ swipe action Összes profil profile dropdown + + All relays failed + Nem sikerült kapcsolódni egyetlen átjátszóhoz sem + No comment provided by engineer. + + + All relays removed + Az összes átjátszó el lett távolítva + No comment provided by engineer. + All reports will be archived for you. Az összes jelentés archiválva lesz az Ön számára. @@ -920,6 +1001,11 @@ swipe action Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra) No comment provided by engineer. + + Allow members to chat with admins. + A csevegés az adminisztrátorokkal engedélyezve van a tagok számára. + No comment provided by engineer. + Allow message reactions only if your contact allows them. A reakciók hozzáadása az üzenetekhez csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. @@ -935,6 +1021,11 @@ swipe action A közvetlen üzenetek küldése a tagok között engedélyezve van. No comment provided by engineer. + + Allow sending direct messages to subscribers. + A közvetlen üzenetek küldése a feliratkozók között engedélyezve van. + No comment provided by engineer. + Allow sending disappearing messages. Az eltűnő üzenetek küldése engedélyezve van. @@ -945,6 +1036,11 @@ swipe action Megosztás engedélyezése No comment provided by engineer. + + Allow subscribers to chat with admins. + A csevegés az adminisztrátorokkal engedélyezve van a feliratkozók számára. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Az elküldött üzenetek végleges törlése engedélyezve van. (24 óra) @@ -1050,11 +1146,6 @@ swipe action Hívás fogadása No comment provided by engineer. - - Anybody can host servers. - Bárki üzemeltethet kiszolgálókat. - No comment provided by engineer. - App build: %@ Alkalmazás összeállítási száma: %@ @@ -1260,6 +1351,23 @@ swipe action Hibás az üzenet kivonata No comment provided by engineer. + + Be free +in your network + Váljon szabaddá +a saját hálózatában + No comment provided by engineer. + + + Be free in your network. + Legyen szabad a saját hálózatában. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe. + No comment provided by engineer. + Better calls Továbbfejlesztett hívásélmény @@ -1357,6 +1465,7 @@ swipe action Block subscriber for all? + Az összes feliratkozó számára letiltja a feliratkozót? No comment provided by engineer. @@ -1409,8 +1518,14 @@ swipe action Mindkét fél küldhet hangüzeneteket. No comment provided by engineer. + + Bottom bar + Alsó sáv + No comment provided by engineer. + Broadcast + Közvetítés… compose placeholder for channel owner @@ -1421,7 +1536,7 @@ swipe action Business address Üzleti cím - No comment provided by engineer. + chat link info line Business chats @@ -1443,15 +1558,6 @@ swipe action A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - A SimpleX Chat használatával Ön elfogadja, hogy: -- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban. -- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. - No comment provided by engineer. - Call already ended! A hívás már véget ért! @@ -1602,48 +1708,80 @@ set passcode view Channel + Csatorna No comment provided by engineer. Channel display name + Csatorna megjelenítendő neve No comment provided by engineer. Channel full name (optional) + Csatorna teljes neve (nem kötelező) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + A csatornának nincsenek aktív átjátszói. Próbáljon meg később csatlakozni. + alert message +alert subtitle + Channel image + Csatornakép No comment provided by engineer. Channel link + Csatornahivatkozás + chat link info line + + + Channel preferences + Csatornabeállítások No comment provided by engineer. Channel profile + Csatornaprofil No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + A csatornaprofil a feliratkozók eszközén és a csevegési átjátszókon van tárolva. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + A csatornaprofil módosult. Ha menti, akkor a frissített profil el lesz küldve a csatorna feliratkozóinak. alert message + + Channel temporarily unavailable + A csatorna ideiglenesen nem érhető el + alert title + Channel will be deleted for all subscribers - this cannot be undone! + A csatorna az összes feliratkozó számára törölve lesz – ez a művelet nem vonható vissza! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + A csatorna törölve lesz az Ön számára – ez a művelet nem vonható vissza! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + A csatorna %2$d átjátszóból %1$d használatával kezd el működni. Folytatja? alert message + + Channels + Csatornák + No comment provided by engineer. + Chat Csevegés @@ -1731,18 +1869,22 @@ set passcode view Chat relay + Csevegési átjátszó No comment provided by engineer. Chat relays + Csevegési átjátszók No comment provided by engineer. Chat relays forward messages in channels you create. + A csevegési átjátszók továbbítják az üzeneteket az Ön által létrehozott csatornákban. No comment provided by engineer. Chat relays forward messages to channel subscribers. + A csevegési átjátszók továbbítják az üzeneteket a csatorna feliratkozóinak. No comment provided by engineer. @@ -1763,7 +1905,8 @@ set passcode view Chat with admins Csevegés az adminisztrátorokkal - chat toolbar + chat feature +chat toolbar Chat with member @@ -1780,11 +1923,26 @@ set passcode view Csevegések No comment provided by engineer. + + Chats with admins are prohibited. + A csevegés az adminisztrátorokkal le van tiltva. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + A nyilvános csatornákban az adminisztrátorokkal való csevegések nem rendelkeznek végpontok közötti titkosítással – csak megbízható csevegési átjátszókkal használja őket. + alert message + Chats with members Csevegés a tagokkal No comment provided by engineer. + + Chats with members are disabled + A csevegés a tagokkal le van tiltva + No comment provided by engineer. + Check messages every 20 min. Üzenetek ellenőrzése 20 percenként. @@ -1797,10 +1955,12 @@ set passcode view Check relay address and try again. + Ellenőrizze az átjátszó címét, és próbálja újra. alert message Check relay name and try again. + Ellenőrizze az átjátszó nevét, és próbálja újra. alert message @@ -1950,11 +2110,7 @@ set passcode view Configure relays - No comment provided by engineer. - - - Configure server operators - Kiszolgálóüzemeltetők beállítása + Átjátszók konfigurálása No comment provided by engineer. @@ -2062,6 +2218,11 @@ Ez a saját egyszer használható meghívója! Kapcsolódás egy hivatkozáson keresztül new chat sheet title + + Connect via link or QR code + Hivatkozás vagy QR-kód használata + No comment provided by engineer. + Connect via one-time link Kapcsolódás az egyszer használható meghívón keresztül @@ -2140,7 +2301,7 @@ Ez a saját egyszer használható meghívója! Connection error (AUTH) Kapcsolódási hiba (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2360,11 @@ Ez a saját egyszer használható meghívója! Kapcsolatok No comment provided by engineer. + + Contact address + Kapcsolattartási cím + chat link info line + Contact allows Partner engedélyezi @@ -2269,6 +2435,11 @@ Ez a saját egyszer használható meghívója! Folytatás No comment provided by engineer. + + Contribute + Közreműködés + No comment provided by engineer. + Conversation deleted! Beszélgetés törölve! @@ -2281,7 +2452,7 @@ Ez a saját egyszer használható meghívója! Copy error - Másolási hiba + Hiba másolása No comment provided by engineer. @@ -2299,11 +2470,6 @@ Ez a saját egyszer használható meghívója! Helyesbíti a nevet a következőre: %@? alert message - - Create - Létrehozás - No comment provided by engineer. - Create 1-time link Egyszer használható meghívó létrehozása @@ -2356,10 +2522,12 @@ Ez a saját egyszer használható meghívója! Create public channel + Nyilvános csatorna létrehozása No comment provided by engineer. Create public channel (BETA) + Nyilvános csatorna létrehozása (BÉTA) No comment provided by engineer. @@ -2372,11 +2540,21 @@ Ez a saját egyszer használható meghívója! Saját cím létrehozása No comment provided by engineer. + + Create your link + Saját hivatkozás létrehozása + No comment provided by engineer. + Create your profile Profil létrehozása No comment provided by engineer. + + Create your public address + Saját nyilvános cím létrehozása + No comment provided by engineer. + Created Létrehozva @@ -2399,6 +2577,7 @@ Ez a saját egyszer használható meghívója! Creating channel + Csatorna létrehozása No comment provided by engineer. @@ -2559,13 +2738,9 @@ Ez a saját egyszer használható meghívója! Kézbesítési hibák felderítése No comment provided by engineer. - - Decentralized - Decentralizált - No comment provided by engineer. - Decode link + Hivatkozás dekódolása relay test step @@ -2616,10 +2791,12 @@ swipe action Delete channel + Csatorna törlése No comment provided by engineer. Delete channel? + Törli a csatornát? No comment provided by engineer. @@ -2795,6 +2972,7 @@ alert button Delete relay + Átjátszó törlése No comment provided by engineer. @@ -2962,6 +3140,16 @@ alert button A tagok közötti közvetlen üzenetek le vannak tiltva. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + A feliratkozók közötti közvetlen üzenetek le vannak tiltva. + No comment provided by engineer. + + + Disable + Letiltás + alert button + Disable (keep overrides) Letiltás (egyéni beállítások megtartása) @@ -3067,6 +3255,11 @@ alert button Az előzmények ne legyenek elküldve az új tagok számára. No comment provided by engineer. + + Do not send history to new subscribers. + Az előzmények ne legyenek elküldve az új feliratkozók számára. + No comment provided by engineer. + Do not use credentials with proxy. Ne használja a hitelesítési adatokat proxyval. @@ -3168,6 +3361,11 @@ chat item action Végpontok között titkosított értesítések. No comment provided by engineer. + + Easier to invite your friends 👋 + Könnyebben hívhatja meg a barátait 👋 + No comment provided by engineer. + Edit Szerkesztés @@ -3175,6 +3373,7 @@ chat item action Edit channel profile + Csatornaprofil szerkesztése No comment provided by engineer. @@ -3190,7 +3389,7 @@ chat item action Enable Engedélyezés - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3214,6 +3413,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Engedélyezzen legalább egy csevegési átjátszót a „Hálózat és kiszolgálók” menüben. channel creation warning @@ -3226,6 +3426,11 @@ chat item action Kamera-hozzáférés engedélyezése No comment provided by engineer. + + Enable chats with admins? + Engedélyezi a csevegést az adminisztrátorokkal? + alert title + Enable disappearing messages by default. Eltűnő üzenetek engedélyezése alapértelmezetten. @@ -3246,16 +3451,16 @@ chat item action Engedélyezi az azonnali értesítéseket? No comment provided by engineer. + + Enable link previews? + Engedélyezi a hivatkozások előnézetét? + alert title + Enable lock Zárolás engedélyezése No comment provided by engineer. - - Enable notifications - Értesítések engedélyezése - No comment provided by engineer. - Enable periodic notifications? Engedélyezi az időszakos értesítéseket? @@ -3363,6 +3568,7 @@ chat item action Enter channel name… + Adja meg a csatorna nevét… No comment provided by engineer. @@ -3390,8 +3596,14 @@ chat item action Adja meg a jelszót fentebb a megjelenítéshez! No comment provided by engineer. + + Enter profile name... + Profil nevének megadása… + No comment provided by engineer. + Enter relay name… + Adja meg az átjátszó nevét… No comment provided by engineer. @@ -3422,7 +3634,7 @@ chat item action Error Hiba - No comment provided by engineer. + conn error description Error aborting address change @@ -3451,6 +3663,7 @@ chat item action Error adding relay + Hiba az átjátszó hozzáadásakor alert title @@ -3515,6 +3728,7 @@ chat item action Error creating channel + Hiba a csatorna létrehozásakor alert title @@ -3699,6 +3913,7 @@ chat item action Error saving channel profile + Hiba a csatornaprofil mentésekor No comment provided by engineer. @@ -3766,6 +3981,11 @@ chat item action Hiba történt a kézbesítési jelentések beállításakor! No comment provided by engineer. + + Error sharing channel + Hiba a csatorna megosztásakor + alert title + Error starting chat Hiba történt a csevegés elindításakor @@ -4134,6 +4354,11 @@ server test error Az összes moderátor számára No comment provided by engineer. + + For anyone to reach you + Bárki számára, aki el szeretné érni Önt + No comment provided by engineer. + For chat profile %@: A(z) %@ nevű csevegési profilhoz: @@ -4281,6 +4506,7 @@ Hiba: %2$@ Get link + Hivatkozás megtekintése relay test step @@ -4288,6 +4514,11 @@ Hiba: %2$@ Kapjon értesítést, ha megemlítik. No comment provided by engineer. + + Get started + Vágjunk bele + No comment provided by engineer. + Good afternoon! Jó napot! @@ -4346,7 +4577,7 @@ Hiba: %2$@ Group link Csoporthivatkozás - No comment provided by engineer. + chat link info line Group links @@ -4458,6 +4689,11 @@ Hiba: %2$@ Az előzmények nem lesznek elküldve az új tagok számára. No comment provided by engineer. + + History is not sent to new subscribers. + Az előzmények nem lesznek elküldve az új feliratkozók számára. + No comment provided by engineer. + How SimpleX works Hogyan működik a SimpleX @@ -4558,11 +4794,6 @@ Hiba: %2$@ Azonnal No comment provided by engineer. - - Immune to spam - Védett a kéretlen tartalommal szemben - No comment provided by engineer. - Import Importálás @@ -4705,9 +4936,9 @@ További fejlesztések hamarosan! Kezdeti szerepkör No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + A SimpleX Chat terminálhoz telepítése No comment provided by engineer. @@ -4765,7 +4996,7 @@ További fejlesztések hamarosan! Invalid connection link Érvénytelen kapcsolattartási hivatkozás - No comment provided by engineer. + conn error description Invalid display name! @@ -4789,10 +5020,12 @@ További fejlesztések hamarosan! Invalid relay address! + Érvénytelen az átjátszó címe! alert title Invalid relay name! + Érvénytelen az átjátszó neve! alert title @@ -4830,6 +5063,11 @@ További fejlesztések hamarosan! Tagok meghívása No comment provided by engineer. + + Invite someone privately + Partner meghívása privátban + No comment provided by engineer. + Invite to chat Meghívás a csevegésbe @@ -4908,6 +5146,7 @@ További fejlesztések hamarosan! Join channel + Csatlakozás a csatornához No comment provided by engineer. @@ -4999,10 +5238,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Leave channel + Csatorna elhagyása No comment provided by engineer. Leave channel? + Elhagyja a csatornát? No comment provided by engineer. @@ -5030,6 +5271,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Kevesebb adatforgalom a mobilhálózatokon. No comment provided by engineer. + + Let someone connect to you + Hagyja, hogy valaki elérje Önt + No comment provided by engineer. + Let's talk in SimpleX Chat Beszélgessünk a SimpleX Chatben @@ -5050,6 +5296,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗 No comment provided by engineer. + + Link signature verified. + Hivatkozás aláírása ellenőrizve. + owner verification + Linked desktop options Társított számítógép beállítások @@ -5235,6 +5486,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! A tagok reakciókat adhatnak hozzá az üzenetekhez. No comment provided by engineer. + + Members can chat with admins. + A tagok cseveghetnek az adminisztrátorokkal + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) @@ -5302,6 +5558,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Message error + Üzenethiba No comment provided by engineer. @@ -5399,6 +5656,16 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! %@ összes üzenete meg fog jelenni! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + Ebben a csatornában az üzenetek **nem rendelkeznek végpontok közötti titkosítással**. A csevegési átjátszók láthatják ezeket az üzeneteket. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + Ebben a csatornában az üzenetek nem rendelkeznek végpontok közötti titkosítással. A csevegési átjátszók láthatják ezeket az üzeneteket. + E2EE info chat item + Messages in this chat will never be deleted. Az ebben a csevegésben lévő üzenetek soha nem lesznek törölve. @@ -5416,7 +5683,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Messages were deleted after you selected them. - Az üzeneteket törölték miután kiváasztotta őket. + Az üzeneteket törölték miután kiválasztotta őket. alert message @@ -5429,16 +5696,16 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve. No comment provided by engineer. + + Migrate + Átköltöztetés + No comment provided by engineer. + Migrate device Eszköz átköltöztetése No comment provided by engineer. - - Migrate from another device - Átköltöztetés egy másik eszközről - No comment provided by engineer. - Migrate here Átköltöztetés ide @@ -5559,6 +5826,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Hálózat és kiszolgálók No comment provided by engineer. + + Network commitments + Hálózati kötelezettségvállalások + No comment provided by engineer. + Network connection Hálózati kapcsolat @@ -5569,6 +5841,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Hálózati decentralizáció No comment provided by engineer. + + Network error + Hálózati hiba + conn error description + Network issues - message expired after many attempts to send it. Hálózati problémák – az üzenet többszöri elküldési kísérlet után lejárt. @@ -5584,6 +5861,13 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Hálózatüzemeltető No comment provided by engineer. + + Network routers cannot know +who talks to whom + A hálózati útválasztók nem tudhatják, +hogy ki kivel beszélget + No comment provided by engineer. + Network settings Hálózati beállítások @@ -5599,6 +5883,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Új token status text + + New 1-time link + Új egyszer használható meghívó + No comment provided by engineer. + New Passcode Új jelkód @@ -5626,6 +5915,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! New chat relay + Új csevegési átjátszó No comment provided by engineer. @@ -5698,6 +5988,18 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nem No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + Nincs fiók. Nincs telefonszám. Nincs e-mail-cím. Nincs személyazonosító. +A legbiztonságosabb titkosítás. + No comment provided by engineer. + + + No active relays + Nincsenek aktív átjátszók + No comment provided by engineer. + No app password Nincs alkalmazás jelszó @@ -5705,10 +6007,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No chat relays + Nincsenek csevegési átjátszók No comment provided by engineer. No chat relays enabled. + Nincsenek engedélyezve csevegési átjátszók. servers warning @@ -5856,13 +6160,24 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nincsenek olvasatlan csevegések No comment provided by engineer. - - No user identifiers. - Nincsenek felhasználói azonosítók. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk. + No comment provided by engineer. + + + Non-profit governance + Nonprofit irányítás + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide - Ön itt szuverén. No comment provided by engineer. Not all relays connected + Nem minden átjátszó kapcsolódott alert title @@ -5922,7 +6237,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! OK Rendben - No comment provided by engineer. + alert button Off @@ -5941,11 +6256,21 @@ new chat action Régi adatbázis No comment provided by engineer. + + On your phone, not on servers. + Az eszközön, nem pedig kiszolgálókon. + No comment provided by engineer. + One-time invitation link Egyszer használható meghívó No comment provided by engineer. + + One-time link + Egyszer használható meghívó + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6290,11 @@ VPN engedélyezése szükséges. Az onion kiszolgálók nem lesznek használva. No comment provided by engineer. + + Only channel owners can change channel preferences. + Csak a csatorna tulajdonosai módosíthatják a csatornabeállításokat. + No comment provided by engineer. + Only chat owners can change preferences. Csak a csevegés tulajdonosai módosíthatják a csevegési beállításokat. @@ -6068,7 +6398,8 @@ VPN engedélyezése szükséges. Open Megnyitás - alert action + alert action +alert button Open Settings @@ -6082,6 +6413,7 @@ VPN engedélyezése szükséges. Open channel + Csatorna megnyitása new chat action @@ -6104,6 +6436,11 @@ VPN engedélyezése szükséges. Feltételek megnyitása No comment provided by engineer. + + Open external link? + Megnyitja a külső hivatkozást? + alert title + Open full link Teljes hivatkozás megnyitása @@ -6126,6 +6463,7 @@ VPN engedélyezése szükséges. Open new channel + Új csatorna megnyitása new chat action @@ -6173,6 +6511,17 @@ VPN engedélyezése szükséges. Kiszolgáló-üzemeltető alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Az üzemeltetők kijelentik, hogy: +- függetlenek maradnak +- minimálisra csökkentik a metaadatok használatát +- ellenőrzött, nyílt forráskódú szoftvereket futtatnak + No comment provided by engineer. + Or import archive file Vagy archívumfájl importálása @@ -6193,6 +6542,11 @@ VPN engedélyezése szükséges. Vagy ossza meg biztonságosan ezt a fájlhivatkozást No comment provided by engineer. + + Or show QR in person or via video call. + Vagy mutassa meg a QR-kódot személyesen vagy videóhíváson keresztül. + No comment provided by engineer. + Or show this code Vagy mutassa meg ezt a kódot @@ -6203,6 +6557,11 @@ VPN engedélyezése szükséges. Vagy a privát megosztáshoz No comment provided by engineer. + + Or use this QR - print or show online. + Vagy használja ezt a QR-kódot – nyomtassa ki vagy mutassa meg online. + No comment provided by engineer. + Organize chats into lists Csevegések listákba szervezése @@ -6222,10 +6581,17 @@ VPN engedélyezése szükséges. Owner + Tulajdonos No comment provided by engineer. Owners + Tulajdonosok + No comment provided by engineer. + + + Ownership: you can run your own relays. + Tulajdonjog: saját átjátszókat üzemeltethet. No comment provided by engineer. @@ -6283,6 +6649,11 @@ VPN engedélyezése szükséges. Kép beillesztése No comment provided by engineer. + + Paste link / Scan + Hivatkozás megadása vagy QR-kód beolvasása + No comment provided by engineer. + Paste link to connect! Hivatkozás beillesztése a kapcsolódáshoz! @@ -6439,10 +6810,12 @@ Hiba: %@ Preset relay address + Előre beállított átjátszó címe No comment provided by engineer. Preset relay name + Előre beállított átjátszó neve No comment provided by engineer. @@ -6480,14 +6853,14 @@ Hiba: %@ Adatvédelmi szabályzat és felhasználási feltételek. No comment provided by engineer. - - Privacy redefined - Újraértelmezett adatvédelem + + Privacy: for owners and subscribers. + Adatvédelem: tulajdonosok és előfizetők számára. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. + + Private and secure messaging. + Privát és biztonságos üzenetváltás. No comment provided by engineer. @@ -6532,6 +6905,7 @@ Hiba: %@ Proceed + Folytatás alert action @@ -6559,9 +6933,9 @@ Hiba: %@ Profiltéma No comment provided by engineer. - - Profile update will be sent to your contacts. - A profilfrissítés el lesz küldve a partnerei számára. + + Profile update will be sent to your SimpleX contacts. + A profilfrissítés el lesz küldve a SimpleX partnerei számára. alert message @@ -6569,6 +6943,11 @@ Hiba: %@ A hívások kezdeményezése le van tiltva. No comment provided by engineer. + + Prohibit chats with admins. + A csevegés az adminisztrátorokkal le van tiltva. + No comment provided by engineer. + Prohibit irreversible message deletion. Az elküldött üzenetek végleges törlése le van tiltva. @@ -6599,6 +6978,11 @@ Hiba: %@ A közvetlen üzenetek küldése a tagok között le van tiltva. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + A közvetlen üzenetek küldése a feliratkozók között le van tiltva. + No comment provided by engineer. + Prohibit sending disappearing messages. Az eltűnő üzenetek küldése le van tiltva. @@ -6666,6 +7050,11 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. A proxy jelszót igényel No comment provided by engineer. + + Public channels - speak freely 🚀 + Nyilvános csatornák – mondja el szabadon a véleményét 🚀 + No comment provided by engineer. + Push notifications Leküldéses értesítések @@ -6706,24 +7095,14 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Tudjon meg többet No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + További információ a Használati útmutatóban. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - További információ a [Használati útmutatóban](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). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - További információ a [GitHub-tárolónkban](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + További információ a GitHub-tárolónkban. No comment provided by engineer. @@ -6885,20 +7264,29 @@ swipe action Relay + Átjátszó No comment provided by engineer. Relay address + Átjátszó címe alert title Relay connection failed + Nem sikerült kapcsolódni az átjátszóhoz alert title Relay link + Átjátszóhivatkozás No comment provided by engineer. + + Relay results: + Átjátszóeredmények: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Az átjátszó csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét. @@ -6911,6 +7299,12 @@ swipe action Relay test failed! + Nem sikerült tesztelni az átjátszót! + No comment provided by engineer. + + + Reliability: many relays per channel. + Megbízhatóság: több átjátszó is használható csatornánként. No comment provided by engineer. @@ -6955,10 +7349,12 @@ swipe action Remove subscriber + Feliratkozó eltávolítása No comment provided by engineer. Remove subscriber? + Eltávolítja a feliratkozót? alert title @@ -7196,6 +7592,11 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + Biztonságos webhivatkozások + No comment provided by engineer. + Safely receive files Fájlok biztonságos fogadása @@ -7224,6 +7625,7 @@ chat item action Save (and notify subscribers) + Mentés (és a feliratkozók értesítése) alert button @@ -7241,6 +7643,11 @@ chat item action Mentés és a csoporttagok értesítése No comment provided by engineer. + + Save and notify subscribers + Mentés és a feliratkozók értesítése + No comment provided by engineer. + Save and reconnect Mentés és újrakapcsolódás @@ -7253,10 +7660,12 @@ chat item action Save channel profile + Csatornaprofil mentése No comment provided by engineer. Save channel profile? + Menti a csatornaprofilt? alert title @@ -7401,7 +7810,7 @@ chat item action Search or paste SimpleX link - Keresés vagy SimpleX-hivatkozás beillesztése + Keressen vagy adjon meg egy SimpleX-hivatkozást No comment provided by engineer. @@ -7439,6 +7848,11 @@ chat item action Biztonsági kód No comment provided by engineer. + + Security: owners hold channel keys. + Biztonság: a csatornák kulcsait a tulajdonosok őrzik. + No comment provided by engineer. + Select Kiválasztás @@ -7569,6 +7983,11 @@ chat item action Kérés küldése üzenet nélkül No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Küldje el a hivatkozást bármilyen üzenetváltó alkalmazáson keresztül – ez egy biztonságos módszer – és kérje meg a partnerét, hogy illessze be a SimpleX alkalmazásba. + No comment provided by engineer. + Send them from gallery or custom keyboards. Küldje el őket a galériából vagy az egyéni billentyűzetekről. @@ -7579,6 +7998,11 @@ chat item action Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Legfeljebb az utolsó 100 üzenet elküldése az új feliratkozók számára. + No comment provided by engineer. + Send your private feedback to groups. Küldjön privát visszajelzést a csoportoknak. @@ -7594,6 +8018,11 @@ chat item action A kérés küldője törölhette a kapcsolódási kérést. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + A hivatkozáselőnézet küldése felfedheti az Ön IP-címét a weboldal számára. Ezt később módosíthatja az adatvédelmi beállításokban. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára. @@ -7721,6 +8150,7 @@ chat item action Server requires authorization to connect to relay, check password. + A kiszolgáló hitelesítést igényel az átjátszóhoz való kapcsolódáshoz, ellenőrizze a jelszavát. relay test error @@ -7853,6 +8283,16 @@ chat item action A beállítások módosultak. alert message + + Setup notifications + Értesítések beállítása + No comment provided by engineer. + + + Setup routers + Útválasztók beállítása + No comment provided by engineer. + Shape profile images Profilkép alakzata @@ -7889,11 +8329,16 @@ chat item action Cím nyilvános megosztása No comment provided by engineer. - - Share address with contacts? - Megosztja a címet a partnereivel? + + Share address with SimpleX contacts? + Megosztja a címet a SimpleX partnereivel? alert title + + Share channel + Csatorna megosztása + No comment provided by engineer. + Share from other apps. Megosztás más alkalmazásokból. @@ -7921,6 +8366,7 @@ chat item action Share relay address + Átjátszó címének megosztása No comment provided by engineer. @@ -7933,9 +8379,14 @@ chat item action Megosztás a SimpleXben No comment provided by engineer. - - Share with contacts - Megosztás a partnerekkel + + Share via chat + Megosztás egy csevegésen keresztül + No comment provided by engineer. + + + Share with SimpleX contacts + Megosztás a SimpleX partnerekkel No comment provided by engineer. @@ -8050,7 +8501,7 @@ chat item action SimpleX address and 1-time links are safe to share via any messenger. - A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül. + A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó alkalmazáson keresztül. No comment provided by engineer. @@ -8110,6 +8561,7 @@ chat item action SimpleX relay address + SimpleX-átjátszó címe simplex link type @@ -8185,6 +8637,11 @@ report reason Négyzet, kör vagy bármi a kettő között. No comment provided by engineer. + + Star on GitHub + Csillagozás a GitHubon + No comment provided by engineer. + Start chat Csevegés elindítása @@ -8287,19 +8744,74 @@ report reason Subscriber + Feliratkozó No comment provided by engineer. + + Subscriber reports + Feliratkozók jelentései + chat feature + Subscriber will be removed from channel - this cannot be undone! + A feliratkozó el lesz távolítva a csatornából – ez a művelet nem vonható vissza! alert message Subscribers + Feliratkozók + No comment provided by engineer. + + + Subscribers can add message reactions. + A feliratkozók reakciókat adhatnak hozzá az üzenetekhez. + No comment provided by engineer. + + + Subscribers can chat with admins. + A feliratkozók cseveghetnek az adminisztrátorokkal. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + A feliratkozók véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + A feliratkozók jelenthetik az üzeneteket a moderátorok felé. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + A feliratkozók küldhetnek SimpleX-hivatkozásokat. + No comment provided by engineer. + + + Subscribers can send direct messages. + A feliratkozók küldhetnek egymásnak közvetlen üzeneteket. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + A feliratkozók küldhetnek eltűnő üzeneteket. + No comment provided by engineer. + + + Subscribers can send files and media. + A feliratkozók küldhetnek fájlokat és médiatartalmakat. + No comment provided by engineer. + + + Subscribers can send voice messages. + A feliratkozók küldhetnek hangüzeneteket. No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz. +Az átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához. No comment provided by engineer. @@ -8382,6 +8894,11 @@ Relay address was used to set up this relay for the channel. Kép készítése No comment provided by engineer. + + Talk to someone + Beszélgessen valakivel + No comment provided by engineer. + Tap Connect to chat Koppintson a „Kapcsolódás” gombra a csevegéshez @@ -8397,13 +8914,9 @@ Relay address was used to set up this relay for the channel. Koppintson a „Kapcsolódás” gombra a bot használatához No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. - No comment provided by engineer. - Tap Join channel + Koppintson a „Csatlakozás a csatornához” gombra No comment provided by engineer. @@ -8436,6 +8949,11 @@ Relay address was used to set up this relay for the channel. Koppintson ide az inkognitóban való kapcsolódáshoz No comment provided by engineer. + + Tap to open + Koppintson ide a megnyitáshoz + No comment provided by engineer. + Tap to paste link Koppintson ide a hivatkozás beillesztéséhez @@ -8464,6 +8982,7 @@ server test failure Test relay + Átjátszó tesztelése No comment provided by engineer. @@ -8520,6 +9039,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The app removed this message after %lld attempts to receive it. + Az alkalmazás %lld sikertelen letöltési kísérlet után eltávolította ezt az üzenetet. No comment provided by engineer. @@ -8537,6 +9057,11 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A beolvasott QR-kód nem egy SimpleX-hivatkozás. No comment provided by engineer. + + The connection reached the limit of undelivered messages + A kapcsolat elérte a kézbesítetlen üzenetek korlátját + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van. @@ -8562,9 +9087,11 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! No comment provided by engineer. - - The future of messaging - Az üzenetváltás jövője + + The first network where you own +your contacts and groups. + Az első hálózat, ahol Ön birtokolja +a saját kapcsolatait és csoportjait. No comment provided by engineer. @@ -8602,6 +9129,11 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + A legrégebbi emberi szabadság - beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének - olyan infrastruktúrán alapul, amely nem tudja elárulni. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**. @@ -8647,6 +9179,16 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Témák No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot - nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött - telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá. + No comment provided by engineer. + These conditions will also apply for: **%@**. Ezek a feltételek lesznek elfogadva a következő számára is: **%@**. @@ -8714,10 +9256,12 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. This is a chat relay address, it cannot be used to connect. + Ez egy csevegési átjátszó címe, nem használható kapcsolódásra. alert message This is your link for channel %@! + Ez a saját hivatkozása a(z) %@ nevű csatornához! new chat action @@ -8770,6 +9314,11 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Kéretlen üzenetek elrejtése. No comment provided by engineer. + + To make SimpleX Network last. + A SimpleX hálózat hosszú távú működésének biztosítása érdekében. + No comment provided by engineer. + To make a new connection Új kapcsolat létrehozásához @@ -8857,11 +9406,6 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal. No comment provided by engineer. - - Toggle chat list: - Csevegési lista ki/be: - No comment provided by engineer. - Toggle incognito when connecting. Inkognitó profil használata kapcsolódáskor ki/be. @@ -8877,6 +9421,11 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Eszköztár átlátszatlansága No comment provided by engineer. + + Top bar + Felső sáv + No comment provided by engineer. + Total Összes kapcsolat @@ -8944,6 +9493,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unblock subscriber for all? + Az összes feliratkozó számára feloldja a feliratkozó letiltását? No comment provided by engineer. @@ -9046,13 +9596,18 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Unsupported connection link Nem támogatott kapcsolattartási hivatkozás - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Legfeljebb az utolsó 100 üzenet lesz elküldve az új feliratkozók számára. + No comment provided by engineer. + Update Frissítés @@ -9178,11 +9733,6 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso A 443-as TCP-port használata kizárólag az előre beállított kiszolgálókhoz. No comment provided by engineer. - - Use chat - SimpleX Chat használata - No comment provided by engineer. - Use current profile Jelenlegi profil használata @@ -9200,6 +9750,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use for new channels + Használat új csatornákhoz No comment provided by engineer. @@ -9244,6 +9795,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use relay + Átjátszó használata No comment provided by engineer. @@ -9266,6 +9818,11 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Alkalmazás egy kézzel való használata. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Használja ezt a címet a közösségi oldalakon használt profiljaiban, weboldalakon vagy az e-mail aláírásában. + No comment provided by engineer. + Use web port Webport használata @@ -9288,6 +9845,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Verify + Ellenőrzés relay test step @@ -9412,12 +9970,19 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Wait + Várakozás alert action Wait response + Várakozás a válaszra relay test step + + Waiting for channel owner to add relays. + Várakozás a csatorna tulajdonosára az átjátszók hozzáadásához. + No comment provided by engineer. + Waiting for desktop... Várakozás a számítógép-alkalmazásra… @@ -9458,6 +10023,11 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Figyelmeztetés: néhány adat elveszhet! No comment provided by engineer. + + We made connecting simpler for new users. + Az új felhasználók számára egyszerűbbé tettük a kapcsolatok létrehozását. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE-kiszolgálók @@ -9508,6 +10078,11 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt. No comment provided by engineer. + + Why SimpleX is built. + Miért jött létre a SimpleX? + No comment provided by engineer. + WiFi Wi-Fi @@ -9722,6 +10297,7 @@ Megismétli a csatlakozási kérést? You can share a link or a QR code - anybody will be able to join the channel. + Megoszthat egy hivatkozást vagy egy QR-kódot – bárki képes lesz csatlakozni a csatornához. No comment provided by engineer. @@ -9769,8 +10345,18 @@ Megismétli a csatlakozási kérést? Ön nem tud üzeneteket küldeni! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + Ön kijelenti, hogy: +- nyilvános csoportokban kizárólag megengedett tartalmakat oszt meg +- tiszteletben tartja a többi felhasználót – nem küld senkinek kéretlen tartalmat + No comment provided by engineer. + You connected to the channel via this relay link. + Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához. No comment provided by engineer. @@ -9778,11 +10364,6 @@ Megismétli a csatlakozási kérést? Nem sikerült ellenőrizni; próbálja meg újra. No comment provided by engineer. - - You decide who can connect. - Ön dönti el, hogy kivel beszélget. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9850,6 +10431,11 @@ Megismétli a kapcsolódási kérést? Ön megkapja az értesítéseket. token info + + You were born without an account + Fiók nélkül születtünk. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Csak azután tud üzeneteket küldeni, **miután a kérését elfogadták**. @@ -9887,6 +10473,7 @@ Megismétli a kapcsolódási kérést? You will stop receiving messages from this channel. Chat history will be preserved. + Ön nem fog több üzenetet kapni ebből a csatornából. A csevegési előzmények megmaradnak. No comment provided by engineer. @@ -9936,6 +10523,7 @@ Megismétli a kapcsolódási kérést? Your channel + Saját csatorna No comment provided by engineer. @@ -9988,6 +10576,11 @@ Megismétli a kapcsolódási kérést? A partnereivel továbbra is kapcsolatban marad. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános. + No comment provided by engineer. + Your credentials may be sent unencrypted. A hitelesítési adatai titkosítatlanul is elküldhetők. @@ -10008,6 +10601,11 @@ Megismétli a kapcsolódási kérést? Saját csoport No comment provided by engineer. + + Your network + Saját hálózat + No comment provided by engineer. + Your preferences Beállítások @@ -10026,6 +10624,8 @@ Megismétli a kapcsolódási kérést? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + A(z) **%@** nevű profilja meg lesz osztva a csatorna átjátszóival és feliratkozóival. +Az átjátszók hozzáférhetnek a csatornaüzenetekhez. No comment provided by engineer. @@ -10048,6 +10648,11 @@ Relays can access channel messages. A profilja módosult. Ha menti, akkor a profilfrissítés el lesz küldve a partnerei számára. alert message + + Your public address + Saját nyilvános cím + No comment provided by engineer. + Your random profile Véletlenszerű profil @@ -10055,15 +10660,17 @@ Relays can access channel messages. Your relay address + Saját átjátszó címe No comment provided by engineer. Your relay name + Saját átjátszó neve No comment provided by engineer. Your server address - Saját SMP-kiszolgálójának címe + Saját SMP-kiszolgáló címe No comment provided by engineer. @@ -10076,21 +10683,11 @@ Relays can access channel messages. Beállítások No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Közreműködés](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Küldjön nekünk e-mailt](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_dőlt_ @@ -10108,6 +10705,7 @@ Relays can access channel messages. accepted + elfogadva No comment provided by engineer. @@ -10132,6 +10730,7 @@ Relays can access channel messages. active + aktív No comment provided by engineer. @@ -10245,6 +10844,11 @@ marked deleted chat item preview text hívás… call status + + can't broadcast + nem lehet közvetíteni + No comment provided by engineer. + can't send messages nem lehet üzeneteket küldeni @@ -10282,10 +10886,12 @@ marked deleted chat item preview text channel + csatorna shown as sender role for channel messages channel profile updated + csatornaprofil frissítve snd group event chat item @@ -10436,6 +11042,7 @@ pref value deleted channel + törölt csatorna rcv group event chat item @@ -10550,6 +11157,7 @@ pref value error: %@ + hiba: %@ receive error chat item @@ -10684,6 +11292,7 @@ pref value link + hivatkozás No comment provided by engineer. @@ -10758,6 +11367,7 @@ pref value new + új No comment provided by engineer. @@ -10885,6 +11495,7 @@ time to disappear relay + átjátszó member role @@ -10899,8 +11510,14 @@ time to disappear removed (%d attempts) + eltávolítva (%d kísérlet) receive error chat item + + removed by operator + az üzemeltető eltávolította + No comment provided by engineer. + removed contact address eltávolította a kapcsolattartási címet @@ -11057,6 +11674,7 @@ utoljára fogadott üzenet: %2$@ updated channel profile + frissített csatornaprofil rcv group event chat item @@ -11081,6 +11699,7 @@ utoljára fogadott üzenet: %2$@ via %@ + a következőn keresztül: %@ relay hostname @@ -11160,6 +11779,7 @@ utoljára fogadott üzenet: %2$@ you are subscriber + Ön feliratkozó No comment provided by engineer. @@ -11222,6 +11842,11 @@ utoljára fogadott üzenet: %2$@ \~áthúzott~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Nem sikerült ellenőrizni az aláírást: %@. + owner verification + @@ -11236,7 +11861,7 @@ utoljára fogadott üzenet: %2$@ SimpleX needs camera access to scan QR codes to connect to other users and for video calls. - A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz. + A SimpleXnek hozzáférésre van szüksége a kamerához a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz. Privacy - Camera Usage Description @@ -11251,7 +11876,7 @@ utoljára fogadott üzenet: %2$@ SimpleX needs microphone access for audio and video calls, and to record voice messages. - A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. + A SimpleXnek hozzáférésre van szüksége a mikrofonhoz a hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. Privacy - Microphone Usage Description diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 2d0566afc6..4d9fce7b4f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -185,9 +185,23 @@ %d mesi time interval - - %d relays - channel relay bar + + %d relays failed + %d relay falliti + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d relay non attivi + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d relay rimossi + channel relay bar +channel subscriber relay bar %d sec @@ -206,10 +220,12 @@ %d subscriber + %d iscritto channel subscriber count %d subscribers + %d iscritti channel subscriber count @@ -219,21 +235,45 @@ %1$d/%2$d relays active + %1$d/%2$d relay attivo/i channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d relay attivi, %3$d errori + channel relay bar + %1$d/%2$d relays active, %3$d failed + %1$d/%2$d relay attivo/i, %3$d fallito/i channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d relay attivi, %3$d rimossi + channel relay bar %1$d/%2$d relays connected + %1$d/%2$d relay connesso/i channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + %1$d/%2$d relay connesso/i, %3$d errori + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d relay connessi, %3$d falliti + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d relay connessi, %3$d rimossi + channel subscriber relay bar %lld @@ -247,6 +287,7 @@ channel relay bar progress with errors %lld channel events + %lld eventi del canale No comment provided by engineer. @@ -349,11 +390,21 @@ channel relay bar progress with errors %u messaggi saltati. No comment provided by engineer. + + (from owner) + (dal proprietario) + chat link info line + (new) (nuovo) No comment provided by engineer. + + (signed) + (firmato) + chat link info line + (this device v%@) (questo dispositivo v%@) @@ -401,6 +452,7 @@ channel relay bar progress with errors **Test relay** to retrieve its name. + **Prova il relay** per recuperare il suo nome. No comment provided by engineer. @@ -446,6 +498,15 @@ channel relay bar progress with errors - e altro ancora! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - scegli se inviare anteprime dei link. +- previeni il phishing dei collegamenti ipertestuali. +- rimuovi il tracciamento dei link. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +605,11 @@ time interval Qualche altra cosa No comment provided by engineer. + + A link for one person to connect + Un link per una persona da connettere + No comment provided by engineer. + A new contact Un contatto nuovo @@ -670,9 +736,9 @@ swipe action Connessioni attive 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. - Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti di SimpleX possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX. No comment provided by engineer. @@ -740,6 +806,11 @@ swipe action Server dei messaggi aggiunti No comment provided by engineer. + + Adding relays will be supported later. + L'aggiunta di relay verrà supportata prossimamente. + No comment provided by engineer. + Additional accent Principale aggiuntivo @@ -860,6 +931,16 @@ swipe action Tutti gli profili profile dropdown + + All relays failed + Tutti i relay falliti + No comment provided by engineer. + + + All relays removed + Tutti i relay rimossi + No comment provided by engineer. + All reports will be archived for you. Tutte le segnalazioni verranno archiviate per te. @@ -920,6 +1001,11 @@ swipe action Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore) No comment provided by engineer. + + Allow members to chat with admins. + Consenti ai membri di chattare con gli amministratori. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Consenti reazioni ai messaggi solo se il tuo contatto le consente. @@ -935,6 +1021,11 @@ swipe action Permetti l'invio di messaggi diretti ai membri. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Permetti l'invio di messaggi diretti agli iscritti. + No comment provided by engineer. + Allow sending disappearing messages. Permetti l'invio di messaggi a tempo. @@ -945,6 +1036,11 @@ swipe action Consenti la condivisione No comment provided by engineer. + + Allow subscribers to chat with admins. + Consenti agli iscritti di chattare con gli amministratori. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore) @@ -1050,11 +1146,6 @@ swipe action Rispondi alla chiamata No comment provided by engineer. - - Anybody can host servers. - Chiunque può installare i server. - No comment provided by engineer. - App build: %@ Build dell'app: %@ @@ -1232,7 +1323,7 @@ swipe action Auto-accept images - Auto-accetta le immagini + Accetta automaticamente le immagini No comment provided by engineer. @@ -1260,6 +1351,23 @@ swipe action Hash del messaggio errato No comment provided by engineer. + + Be free +in your network + Vivi libero +nella tua rete + No comment provided by engineer. + + + Be free in your network. + Vivi libero nella tua rete. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Perché abbiamo distrutto il potere di sapere chi sei. In modo che il tuo potere non possa mai esserti sottratto. + No comment provided by engineer. + Better calls Chiamate migliorate @@ -1357,6 +1465,7 @@ swipe action Block subscriber for all? + Bloccare l'iscritto per tutti? No comment provided by engineer. @@ -1409,8 +1518,14 @@ swipe action Sia tu che il tuo contatto potete inviare messaggi vocali. No comment provided by engineer. + + Bottom bar + Barra inferiore + No comment provided by engineer. + Broadcast + Trasmetti compose placeholder for channel owner @@ -1421,7 +1536,7 @@ swipe action Business address Indirizzo di lavoro - No comment provided by engineer. + chat link info line Business chats @@ -1443,15 +1558,6 @@ swipe action Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Usando SimpleX Chat accetti di: -- inviare solo contenuto legale nei gruppi pubblici. -- rispettare gli altri utenti - niente spam. - No comment provided by engineer. - Call already ended! Chiamata già terminata! @@ -1602,48 +1708,80 @@ set passcode view Channel + Canale No comment provided by engineer. Channel display name + Nome da mostrare del canale No comment provided by engineer. Channel full name (optional) + Nome completo del canale (facoltativo) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + Il canale non ha relay attivi. Prova a iscriverti più tardi. + alert message +alert subtitle + Channel image + Immagine del canale No comment provided by engineer. Channel link + Link del canale + chat link info line + + + Channel preferences + Preferenze del canale No comment provided by engineer. Channel profile + Profilo del canale No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + Il profilo del canale è memorizzato sui dispositivi degli iscritti e sui relay di chat. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + Il profilo del canale è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato agli iscritti di canale. alert message + + Channel temporarily unavailable + Canale non disponibile temporaneamente + alert title + Channel will be deleted for all subscribers - this cannot be undone! + Il canale verrà eliminato per tutti gli iscritti, non è reversibile! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + Il canale verrà eliminato per te, non è reversibile! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + Il canale sarà operativo con %1$d di %2$d relay. Procedere? alert message + + Channels + Canali + No comment provided by engineer. + Chat Chat @@ -1731,18 +1869,22 @@ set passcode view Chat relay + Relay di chat No comment provided by engineer. Chat relays + Relay di chat No comment provided by engineer. Chat relays forward messages in channels you create. + I relay di chat inoltrano i messaggi nei canali che crei. No comment provided by engineer. Chat relays forward messages to channel subscribers. + I relay di chat inoltrano i messaggi agli iscritti del canale. No comment provided by engineer. @@ -1763,7 +1905,8 @@ set passcode view Chat with admins Chat con amministratori - chat toolbar + chat feature +chat toolbar Chat with member @@ -1780,11 +1923,26 @@ set passcode view Chat No comment provided by engineer. + + Chats with admins are prohibited. + Le chat con gli amministratori sono vietate. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Le chat con amministratori in canali pubblici non hanno crittografia E2E: usale solo con relay di chat fidati. + alert message + Chats with members Chat con membri No comment provided by engineer. + + Chats with members are disabled + Le chat con i membri sono disattivate + No comment provided by engineer. + Check messages every 20 min. Controlla i messaggi ogni 20 min. @@ -1797,10 +1955,12 @@ set passcode view Check relay address and try again. + Controlla l'indirizzo del relay e riprova. alert message Check relay name and try again. + Controlla il nome del relay e riprova. alert message @@ -1950,11 +2110,7 @@ set passcode view Configure relays - No comment provided by engineer. - - - Configure server operators - Configura gli operatori dei server + Configura i relay No comment provided by engineer. @@ -2062,6 +2218,11 @@ Questo è il tuo link una tantum! Connetti via link new chat sheet title + + Connect via link or QR code + Connetti via link o codice QR + No comment provided by engineer. + Connect via one-time link Connetti via link una tantum @@ -2140,7 +2301,7 @@ Questo è il tuo link una tantum! Connection error (AUTH) Errore di connessione (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2360,11 @@ Questo è il tuo link una tantum! Connessioni No comment provided by engineer. + + Contact address + Indirizzo di contatto + chat link info line + Contact allows Il contatto lo consente @@ -2269,6 +2435,11 @@ Questo è il tuo link una tantum! Continua No comment provided by engineer. + + Contribute + Contribuisci + No comment provided by engineer. + Conversation deleted! Conversazione eliminata! @@ -2299,11 +2470,6 @@ Questo è il tuo link una tantum! Correggere il nome a %@? alert message - - Create - Crea - No comment provided by engineer. - Create 1-time link Crea link una tantum @@ -2356,10 +2522,12 @@ Questo è il tuo link una tantum! Create public channel + Crea canale pubblico No comment provided by engineer. Create public channel (BETA) + Crea canale pubblico (BETA) No comment provided by engineer. @@ -2372,11 +2540,21 @@ Questo è il tuo link una tantum! Crea il tuo indirizzo No comment provided by engineer. + + Create your link + Connettiti con qualcuno + No comment provided by engineer. + Create your profile Crea il tuo profilo No comment provided by engineer. + + Create your public address + Crea il tuo indirizzo pubblico + No comment provided by engineer. + Created Creato @@ -2399,6 +2577,7 @@ Questo è il tuo link una tantum! Creating channel + Creazione canale No comment provided by engineer. @@ -2559,13 +2738,9 @@ Questo è il tuo link una tantum! Debug della consegna No comment provided by engineer. - - Decentralized - Decentralizzato - No comment provided by engineer. - Decode link + Decodifica il link relay test step @@ -2616,10 +2791,12 @@ swipe action Delete channel + Elimina canale No comment provided by engineer. Delete channel? + Eliminare il canale? No comment provided by engineer. @@ -2795,6 +2972,7 @@ alert button Delete relay + Elimina relay No comment provided by engineer. @@ -2962,6 +3140,16 @@ alert button I messaggi diretti tra i membri sono vietati in questo gruppo. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + I messaggi diretti tra gli iscritti sono vietati. + No comment provided by engineer. + + + Disable + Disattiva + alert button + Disable (keep overrides) Disattiva (mantieni sostituzioni) @@ -3067,6 +3255,11 @@ alert button Non inviare la cronologia ai nuovi membri. No comment provided by engineer. + + Do not send history to new subscribers. + Non inviare la cronologia ai nuovi iscritti. + No comment provided by engineer. + Do not use credentials with proxy. Non usare credenziali con proxy. @@ -3168,6 +3361,11 @@ chat item action Notifiche crittografate E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + È più facile invitare i tuoi amici 👋 + No comment provided by engineer. + Edit Modifica @@ -3175,6 +3373,7 @@ chat item action Edit channel profile + Modifica profilo canale No comment provided by engineer. @@ -3190,7 +3389,7 @@ chat item action Enable Attiva - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3214,6 +3413,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Attiva almeno un relay di chat in "Rete e server". channel creation warning @@ -3226,6 +3426,11 @@ chat item action Attiva l'accesso alla fotocamera No comment provided by engineer. + + Enable chats with admins? + Attivare le chat con gli amministratori? + alert title + Enable disappearing messages by default. Attiva i messaggi a tempo in modo predefinito. @@ -3246,16 +3451,16 @@ chat item action Attivare le notifiche istantanee? No comment provided by engineer. + + Enable link previews? + Attivare le anteprime dei link? + alert title + Enable lock Attiva blocco No comment provided by engineer. - - Enable notifications - Attiva le notifiche - No comment provided by engineer. - Enable periodic notifications? Attivare le notifiche periodiche? @@ -3363,6 +3568,7 @@ chat item action Enter channel name… + Inserisci il nome del canale… No comment provided by engineer. @@ -3390,8 +3596,14 @@ chat item action Inserisci la password sopra per mostrare! No comment provided by engineer. + + Enter profile name... + Inserisci nome profilo... + No comment provided by engineer. + Enter relay name… + Inserisci il nome del relay… No comment provided by engineer. @@ -3422,7 +3634,7 @@ chat item action Error Errore - No comment provided by engineer. + conn error description Error aborting address change @@ -3451,6 +3663,7 @@ chat item action Error adding relay + Errore di aggiunta del relay alert title @@ -3515,6 +3728,7 @@ chat item action Error creating channel + Errore di creazione del canale alert title @@ -3699,6 +3913,7 @@ chat item action Error saving channel profile + Errore di salvataggio del profilo del canale No comment provided by engineer. @@ -3766,6 +3981,11 @@ chat item action Errore nell'impostazione delle ricevute di consegna! No comment provided by engineer. + + Error sharing channel + Errore nella condivisione del canale + alert title + Error starting chat Errore di avvio della chat @@ -4134,6 +4354,11 @@ server test error Per tutti i moderatori No comment provided by engineer. + + For anyone to reach you + Per chiunque debba raggiungerti + No comment provided by engineer. + For chat profile %@: Per il profilo di chat %@: @@ -4281,6 +4506,7 @@ Errore: %2$@ Get link + Ottieni link relay test step @@ -4288,6 +4514,11 @@ Errore: %2$@ Ricevi una notifica quando menzionato. No comment provided by engineer. + + Get started + Cominciamo + No comment provided by engineer. + Good afternoon! Buon pomeriggio! @@ -4346,7 +4577,7 @@ Errore: %2$@ Group link Link del gruppo - No comment provided by engineer. + chat link info line Group links @@ -4458,6 +4689,11 @@ Errore: %2$@ La cronologia non viene inviata ai nuovi membri. No comment provided by engineer. + + History is not sent to new subscribers. + La cronologia non viene inviata ai nuovi iscritti. + No comment provided by engineer. + How SimpleX works Come funziona SimpleX @@ -4558,11 +4794,6 @@ Errore: %2$@ Immediatamente No comment provided by engineer. - - Immune to spam - Immune a spam e abusi - No comment provided by engineer. - Import Importa @@ -4705,9 +4936,9 @@ Altri miglioramenti sono in arrivo! Ruolo iniziale No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installa Simplex Chat per terminale No comment provided by engineer. @@ -4765,7 +4996,7 @@ Altri miglioramenti sono in arrivo! Invalid connection link Link di connessione non valido - No comment provided by engineer. + conn error description Invalid display name! @@ -4789,10 +5020,12 @@ Altri miglioramenti sono in arrivo! Invalid relay address! + Indirizzo del relay non valido! alert title Invalid relay name! + Nome del relay non valido! alert title @@ -4830,6 +5063,11 @@ Altri miglioramenti sono in arrivo! Invita membri No comment provided by engineer. + + Invite someone privately + Invita qualcuno in modo privato + No comment provided by engineer. + Invite to chat Invita in chat @@ -4908,6 +5146,7 @@ Altri miglioramenti sono in arrivo! Join channel + Iscriviti al canale No comment provided by engineer. @@ -4999,10 +5238,12 @@ Questo è il tuo link per il gruppo %@! Leave channel + Esci dal canale No comment provided by engineer. Leave channel? + Uscire dal canale? No comment provided by engineer. @@ -5030,6 +5271,11 @@ Questo è il tuo link per il gruppo %@! Meno traffico sulle reti mobili. No comment provided by engineer. + + Let someone connect to you + Lascia che qualcuno si connetta a te + No comment provided by engineer. + Let's talk in SimpleX Chat Parliamo in SimpleX Chat @@ -5050,6 +5296,11 @@ Questo è il tuo link per il gruppo %@! Collega le app mobile e desktop! 🔗 No comment provided by engineer. + + Link signature verified. + Firma del link verificata. + owner verification + Linked desktop options Opzioni del desktop collegato @@ -5235,6 +5486,11 @@ Questo è il tuo link per il gruppo %@! I membri del gruppo possono aggiungere reazioni ai messaggi. No comment provided by engineer. + + Members can chat with admins. + I membri possono chattare con gli amministratori. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) @@ -5302,6 +5558,7 @@ Questo è il tuo link per il gruppo %@! Message error + Errore del messaggio No comment provided by engineer. @@ -5399,6 +5656,16 @@ Questo è il tuo link per il gruppo %@! I messaggi da %@ verranno mostrati! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + I messaggi in questo canale **non sono crittografati end-to-end**. I relay di chat possono vedere questi messaggi. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + I messaggi in questo canale non sono crittografati end-to-end. I relay di chat possono vedere questi messaggi. + E2EE info chat item + Messages in this chat will never be deleted. I messaggi in questa chat non verranno mai eliminati. @@ -5429,16 +5696,16 @@ Questo è il tuo link per il gruppo %@! I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione. No comment provided by engineer. + + Migrate + Migra + No comment provided by engineer. + Migrate device Migra dispositivo No comment provided by engineer. - - Migrate from another device - Migra da un altro dispositivo - No comment provided by engineer. - Migrate here Migra qui @@ -5559,6 +5826,11 @@ Questo è il tuo link per il gruppo %@! Rete e server No comment provided by engineer. + + Network commitments + Impegni sulla rete + No comment provided by engineer. + Network connection Connessione di rete @@ -5569,6 +5841,11 @@ Questo è il tuo link per il gruppo %@! Decentralizzazione della rete No comment provided by engineer. + + Network error + Errore di rete + conn error description + Network issues - message expired after many attempts to send it. Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo. @@ -5584,6 +5861,13 @@ Questo è il tuo link per il gruppo %@! Operatore di rete No comment provided by engineer. + + Network routers cannot know +who talks to whom + Gli instradatori di rete non possono +sapere chi parla con chi + No comment provided by engineer. + Network settings Impostazioni di rete @@ -5599,6 +5883,11 @@ Questo è il tuo link per il gruppo %@! Nuovo token status text + + New 1-time link + Nuovo link una tantum + No comment provided by engineer. + New Passcode Nuovo codice di accesso @@ -5626,6 +5915,7 @@ Questo è il tuo link per il gruppo %@! New chat relay + Nuovo relay di chat No comment provided by engineer. @@ -5698,6 +5988,18 @@ Questo è il tuo link per il gruppo %@! No No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + Nessun account. Nessun telefono. Nessuna email. Nessun identificatore. +La crittografia più sicura. + No comment provided by engineer. + + + No active relays + Nessun relay attivo + No comment provided by engineer. + No app password Nessuna password dell'app @@ -5705,10 +6007,12 @@ Questo è il tuo link per il gruppo %@! No chat relays + Nessun relay di chat No comment provided by engineer. No chat relays enabled. + Nessun relay di chat attivato. servers warning @@ -5856,13 +6160,24 @@ Questo è il tuo link per il gruppo %@! Nessuna chat non letta No comment provided by engineer. - - No user identifiers. - Nessun identificatore utente. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nessuno monitorava le tue conversazioni. Nessuno disegnava una mappa delle tue posizioni. La privacy non era mai stata una caratteristica, era uno stile di vita. + No comment provided by engineer. + + + Non-profit governance + Organizzazione non a scopo di lucro + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Non una serratura migliore sulla porta di qualcun altro. Non un padrone di casa più gentile che rispetta la tua privacy, ma che continua a tenere traccia di tutti i visitatori. Non sei un ospite. Sei a casa tua. Nessun re può entrarvi: sei tu il sovrano. No comment provided by engineer. Not all relays connected + Non tutti i relay sono connessi alert title @@ -5922,7 +6237,7 @@ Questo è il tuo link per il gruppo %@! OK OK - No comment provided by engineer. + alert button Off @@ -5941,11 +6256,21 @@ new chat action Database vecchio No comment provided by engineer. + + On your phone, not on servers. + Sul tuo telefono, non sui server. + No comment provided by engineer. + One-time invitation link Link di invito una tantum No comment provided by engineer. + + One-time link + Link una tantum + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6290,11 @@ Richiede l'attivazione della VPN. Gli host Onion non verranno usati. No comment provided by engineer. + + Only channel owners can change channel preferences. + Solo i proprietari del canale possono modificarne le preferenze. + No comment provided by engineer. + Only chat owners can change preferences. Solo i proprietari della chat possono modificarne le preferenze. @@ -6068,7 +6398,8 @@ Richiede l'attivazione della VPN. Open Apri - alert action + alert action +alert button Open Settings @@ -6082,6 +6413,7 @@ Richiede l'attivazione della VPN. Open channel + Apri canale new chat action @@ -6104,6 +6436,11 @@ Richiede l'attivazione della VPN. Apri le condizioni No comment provided by engineer. + + Open external link? + Aprire il link esterno? + alert title + Open full link Apri link completo @@ -6126,6 +6463,7 @@ Richiede l'attivazione della VPN. Open new channel + Apri un canale nuovo new chat action @@ -6173,14 +6511,25 @@ Richiede l'attivazione della VPN. Server dell'operatore alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Gli operatori si impegnano a: +- Essere indipendenti +- Minimizzare l'uso di metadati +- Eseguire codice open source verificato + No comment provided by engineer. + Or import archive file - O importa file archivio + O importa un file dell'archivio No comment provided by engineer. Or paste archive link - O incolla il link dell'archivio + O incolla un link dell'archivio No comment provided by engineer. @@ -6193,6 +6542,11 @@ Richiede l'attivazione della VPN. O condividi in modo sicuro questo link del file No comment provided by engineer. + + Or show QR in person or via video call. + O mostra il QR di persona o via videochiamata. + No comment provided by engineer. + Or show this code O mostra questo codice @@ -6203,6 +6557,11 @@ Richiede l'attivazione della VPN. O per condividere in modo privato No comment provided by engineer. + + Or use this QR - print or show online. + O usa questo QR: stampalo o mostralo online. + No comment provided by engineer. + Organize chats into lists Organizza le chat in elenchi @@ -6222,10 +6581,17 @@ Richiede l'attivazione della VPN. Owner + Proprietario No comment provided by engineer. Owners + Proprietari + No comment provided by engineer. + + + Ownership: you can run your own relays. + Proprietà: puoi gestire i tuoi relay personali. No comment provided by engineer. @@ -6283,6 +6649,11 @@ Richiede l'attivazione della VPN. Incolla immagine No comment provided by engineer. + + Paste link / Scan + Incolla link / Scansiona + No comment provided by engineer. + Paste link to connect! Incolla un link per connettere! @@ -6439,10 +6810,12 @@ Errore: %@ Preset relay address + Indirizzo relay preimpostato No comment provided by engineer. Preset relay name + Nome relay preimpostato No comment provided by engineer. @@ -6480,14 +6853,14 @@ Errore: %@ Informativa sulla privacy e condizioni d'uso. No comment provided by engineer. - - Privacy redefined - Privacy ridefinita + + Privacy: for owners and subscribers. + Privacy: per i proprietari e gli iscritti. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. + + Private and secure messaging. + Messaggistica privata e sicura. No comment provided by engineer. @@ -6532,6 +6905,7 @@ Errore: %@ Proceed + Procedi alert action @@ -6559,9 +6933,9 @@ Errore: %@ Tema del profilo No comment provided by engineer. - - Profile update will be sent to your contacts. - L'aggiornamento del profilo verrà inviato ai tuoi contatti. + + Profile update will be sent to your SimpleX contacts. + L'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX. alert message @@ -6569,6 +6943,11 @@ Errore: %@ Proibisci le chiamate audio/video. No comment provided by engineer. + + Prohibit chats with admins. + Vieta le chat con gli amministratori. + No comment provided by engineer. + Prohibit irreversible message deletion. Proibisci l'eliminazione irreversibile dei messaggi. @@ -6599,6 +6978,11 @@ Errore: %@ Proibisci l'invio di messaggi diretti ai membri. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + Proibisci l'invio di messaggi diretti agli iscritti. + No comment provided by engineer. + Prohibit sending disappearing messages. Proibisci l'invio di messaggi a tempo. @@ -6666,6 +7050,11 @@ Attivalo nelle impostazioni *Rete e server*. Il proxy richiede una password No comment provided by engineer. + + Public channels - speak freely 🚀 + Canali pubblici - parla liberamente 🚀 + No comment provided by engineer. + Push notifications Notifiche push @@ -6706,24 +7095,14 @@ Attivalo nelle impostazioni *Rete e server*. Leggi tutto No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Leggi di più nella Guida utente. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Maggiori informazioni nella [Guida per l'utente](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). - Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Maggiori informazioni nel nostro [repository GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Maggiori informazioni nel nostro repository GitHub. No comment provided by engineer. @@ -6885,20 +7264,29 @@ swipe action Relay + Relay No comment provided by engineer. Relay address + Indirizzo del relay alert title Relay connection failed + Connessione del relay fallita alert title Relay link + Link del relay No comment provided by engineer. + + Relay results: + Risultati relay: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP. @@ -6911,6 +7299,12 @@ swipe action Relay test failed! + Prova del relay fallita! + No comment provided by engineer. + + + Reliability: many relays per channel. + Affidabilità: relay multipli per canale. No comment provided by engineer. @@ -6955,10 +7349,12 @@ swipe action Remove subscriber + Rimuovi iscritto No comment provided by engineer. Remove subscriber? + Rimuovere l'iscritto? alert title @@ -7196,6 +7592,11 @@ swipe action Proxy SOCKS No comment provided by engineer. + + Safe web links + Link web sicuri + No comment provided by engineer. + Safely receive files Ricevi i file in sicurezza @@ -7219,11 +7620,12 @@ chat item action Save (and notify members) - Salva (e informa i membri) + Salva (e avvisa i membri) alert button Save (and notify subscribers) + Salva (e avvisa gli iscritti) alert button @@ -7241,6 +7643,11 @@ chat item action Salva e avvisa i membri del gruppo No comment provided by engineer. + + Save and notify subscribers + Salva e avvisa gli iscritti + No comment provided by engineer. + Save and reconnect Salva e riconnetti @@ -7253,10 +7660,12 @@ chat item action Save channel profile + Salva il profilo del canale No comment provided by engineer. Save channel profile? + Salva il profilo del canale? alert title @@ -7351,7 +7760,7 @@ chat item action Scan QR code - Scansiona codice QR + Scansiona un codice QR No comment provided by engineer. @@ -7439,6 +7848,11 @@ chat item action Codice di sicurezza No comment provided by engineer. + + Security: owners hold channel keys. + Sicurezza: solo i proprietari hanno le chiavi del canale. + No comment provided by engineer. + Select Seleziona @@ -7569,6 +7983,11 @@ chat item action Invia richiesta senza messaggio No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Invia il link tramite qualsiasi messenger, è sicuro. Chiedi di incollarlo in SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Inviali dalla galleria o dalle tastiere personalizzate. @@ -7579,6 +7998,11 @@ chat item action Invia fino a 100 ultimi messaggi ai nuovi membri. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Invia fino a 100 ultimi messaggi ai nuovi iscritti. + No comment provided by engineer. + Send your private feedback to groups. Invia i tuoi commenti privati ai gruppi. @@ -7594,6 +8018,11 @@ chat item action Il mittente potrebbe aver eliminato la richiesta di connessione. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + L'invio di un'anteprima del link può rivelare il tuo indirizzo IP al sito. Puoi modificarlo nelle impostazioni di Privacy più tardi. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. L'invio delle ricevute di consegna sarà attivo per tutti i contatti in tutti i profili di chat visibili. @@ -7721,6 +8150,7 @@ chat item action Server requires authorization to connect to relay, check password. + Il server richiede l'autorizzazione per connettersi al relay, controlla la password. relay test error @@ -7853,6 +8283,16 @@ chat item action Le impostazioni sono state cambiate. alert message + + Setup notifications + Configura le notifiche + No comment provided by engineer. + + + Setup routers + Configura gli instradatori + No comment provided by engineer. + Shape profile images Forma delle immagini del profilo @@ -7889,11 +8329,16 @@ chat item action Condividi indirizzo pubblicamente No comment provided by engineer. - - Share address with contacts? - Condividere l'indirizzo con i contatti? + + Share address with SimpleX contacts? + Condividere l'indirizzo con i contatti di SimpleX? alert title + + Share channel + Condividi canale + No comment provided by engineer. + Share from other apps. Condividi da altre app. @@ -7921,6 +8366,7 @@ chat item action Share relay address + Condividi l'indirizzo del relay No comment provided by engineer. @@ -7933,9 +8379,14 @@ chat item action Condividi in SimpleX No comment provided by engineer. - - Share with contacts - Condividi con i contatti + + Share via chat + Condividi via chat + No comment provided by engineer. + + + Share with SimpleX contacts + Condividi con i contatti di SimpleX No comment provided by engineer. @@ -8110,6 +8561,7 @@ chat item action SimpleX relay address + Indirizzo del relay SimpleX simplex link type @@ -8185,6 +8637,11 @@ report reason Quadrata, circolare o qualsiasi forma tra le due. No comment provided by engineer. + + Star on GitHub + Dai una stella su GitHub + No comment provided by engineer. + Start chat Avvia chat @@ -8282,24 +8739,79 @@ report reason Subscribed - Iscritto + Iscritto/a No comment provided by engineer. Subscriber + Iscritto No comment provided by engineer. + + Subscriber reports + Segnalazioni degli iscritti + chat feature + Subscriber will be removed from channel - this cannot be undone! + L'iscritto verrà rimosso dal canale, non è reversibile! alert message Subscribers + Iscritti + No comment provided by engineer. + + + Subscribers can add message reactions. + Gli iscritti al canale possono aggiungere reazioni ai messaggi. + No comment provided by engineer. + + + Subscribers can chat with admins. + Gli iscritti possono chattare con gli amministratori. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Gli iscritti al canale possono eliminare irreversibilmente i messaggi inviati. (24 ore) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Gli iscritti possono segnalare messaggi ai moderatori. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Gli iscritti al canale possono inviare link di Simplex. + No comment provided by engineer. + + + Subscribers can send direct messages. + Gli iscritti al canale possono inviare messaggi diretti. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Gli iscritti al canale possono inviare messaggi a tempo. + No comment provided by engineer. + + + Subscribers can send files and media. + Gli iscritti al canale possono inviare file e contenuti multimediali. + No comment provided by engineer. + + + Subscribers can send voice messages. + Gli iscritti al canale possono inviare messaggi vocali. No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Gli iscritti usano il link del relay per connettersi al canale. +L'indirizzo del relay è stato usato per impostare questo relay per il canale. No comment provided by engineer. @@ -8382,6 +8894,11 @@ Relay address was used to set up this relay for the channel. Scatta foto No comment provided by engineer. + + Talk to someone + Parla con qualcuno + No comment provided by engineer. + Tap Connect to chat Tocca Connetti per chattare @@ -8397,13 +8914,9 @@ Relay address was used to set up this relay for the channel. Tocca Connetti per usare il bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi. - No comment provided by engineer. - Tap Join channel + Tocca Iscriviti al canale No comment provided by engineer. @@ -8436,6 +8949,11 @@ Relay address was used to set up this relay for the channel. Toccare per entrare in incognito No comment provided by engineer. + + Tap to open + Tocca per aprire + No comment provided by engineer. + Tap to paste link Tocca per incollare il link @@ -8464,6 +8982,7 @@ server test failure Test relay + Prova relay No comment provided by engineer. @@ -8520,6 +9039,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The app removed this message after %lld attempts to receive it. + L'app ha rimosso questo messaggio dopo %lld tentativi di riceverlo. No comment provided by engineer. @@ -8537,6 +9057,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il codice che hai scansionato non è un codice QR di link SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + La connessione ha raggiunto il limite di messaggi non consegnati + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline. @@ -8562,9 +9087,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione! No comment provided by engineer. - - The future of messaging - La nuova generazione di messaggistica privata + + The first network where you own +your contacts and groups. + La prima rete in cui possiedi +i tuoi contatti e i tuoi gruppi. No comment provided by engineer. @@ -8602,6 +9129,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + La più antica libertà umana, parlare con un'altra persona senza essere osservati, si basa su un'infrastruttura che non può tradirla. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Le stesse condizioni si applicheranno all'operatore **%@**. @@ -8647,6 +9179,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Temi No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Poi ci siamo trasferiti online e ogni piattaforma ha chiesto un pezzo di noi: il nome, il numero, gli amici. Abbiamo accettato che il prezzo da pagare per comunicare con gli altri fosse quello di far sapere a qualcuno con chi parliamo. Ogni generazione, sia di persone che di tecnologia, ha funzionato così: telefono, email, messenger, social media. Sembrava l'unico modo possibile. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + C'è un'altra via. Una rete senza numeri di telefono. Senza nomi utente. Senza account. Senza identificatori utente di alcun tipo. Una rete che connette le persone e trasferisce messaggi crittografati senza sapere chi è connesso. + No comment provided by engineer. + These conditions will also apply for: **%@**. Queste condizioni si applicheranno anche per: **%@**. @@ -8714,10 +9256,12 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. This is a chat relay address, it cannot be used to connect. + Questo è un indirizzo di relay di chat, non può essere usato per connettersi. alert message This is your link for channel %@! + Questo è il tuo link per il canale %@! new chat action @@ -8770,6 +9314,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Per nascondere messaggi indesiderati. No comment provided by engineer. + + To make SimpleX Network last. + Per la sostenibilità della rete di SimpleX. + No comment provided by engineer. + To make a new connection Per creare una nuova connessione @@ -8857,11 +9406,6 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi. No comment provided by engineer. - - Toggle chat list: - Cambia l'elenco delle chat: - No comment provided by engineer. - Toggle incognito when connecting. Attiva/disattiva l'incognito quando ti colleghi. @@ -8877,6 +9421,11 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Opacità barra degli strumenti No comment provided by engineer. + + Top bar + Barra superiore + No comment provided by engineer. + Total Totale @@ -8944,6 +9493,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Unblock subscriber for all? + Sbloccare l'iscritto per tutti? No comment provided by engineer. @@ -9046,13 +9596,18 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Unsupported connection link Link di connessione non supportato - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Vengono inviati ai nuovi membri fino a 100 ultimi messaggi. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Vengono inviati ai nuovi iscritti fino a 100 ultimi messaggi. + No comment provided by engineer. + Update Aggiorna @@ -9178,11 +9733,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa la porta TCP 443 solo per i server preimpostati. No comment provided by engineer. - - Use chat - Usa la chat - No comment provided by engineer. - Use current profile Usa il profilo attuale @@ -9200,6 +9750,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use for new channels + Usa per canali nuovi No comment provided by engineer. @@ -9244,6 +9795,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use relay + Usa relay No comment provided by engineer. @@ -9266,6 +9818,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa l'app con una mano sola. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Usa questo indirizzo nel tuo profilo di social media, sito web o firma email. + No comment provided by engineer. + Use web port Usa porta web @@ -9288,6 +9845,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Verify + Verifica relay test step @@ -9412,12 +9970,19 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Wait + Attendi alert action Wait response + Attendi risposta relay test step + + Waiting for channel owner to add relays. + In attesa che il proprietario del canale aggiunga dei relay. + No comment provided by engineer. + Waiting for desktop... In attesa del desktop... @@ -9458,6 +10023,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Attenzione: potresti perdere alcuni dati! No comment provided by engineer. + + We made connecting simpler for new users. + Abbiamo semplificato la connessione per i nuovi utenti. + No comment provided by engineer. + WebRTC ICE servers Server WebRTC ICE @@ -9508,6 +10078,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano. No comment provided by engineer. + + Why SimpleX is built. + Perché costruiamo SimpleX. + No comment provided by engineer. + WiFi WiFi @@ -9722,6 +10297,7 @@ Ripetere la richiesta di ingresso? You can share a link or a QR code - anybody will be able to join the channel. + Puoi condividere un link o un codice QR, chiunque sarà in grado di iscriversi al canale. No comment provided by engineer. @@ -9769,8 +10345,18 @@ Ripetere la richiesta di ingresso? Non puoi inviare messaggi! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + Tu ti impegni a: +- Pubblicare solo contenuto legale nei gruppi pubblici +- Rispettare gli altri utenti. Niente spam + No comment provided by engineer. + You connected to the channel via this relay link. + Ti sei connesso/a al canale attraverso questo link del relay. No comment provided by engineer. @@ -9778,11 +10364,6 @@ Ripetere la richiesta di ingresso? Non è stato possibile verificarti, riprova. No comment provided by engineer. - - You decide who can connect. - Sei tu a decidere chi può connettersi. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9850,6 +10431,11 @@ Ripetere la richiesta di connessione? Dovresti ricevere le notifiche. token info + + You were born without an account + Sei nato senza un account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Potrai inviare messaggi **solo dopo che la tua richiesta verrà accettata**. @@ -9887,6 +10473,7 @@ Ripetere la richiesta di connessione? You will stop receiving messages from this channel. Chat history will be preserved. + Smetterai di ricevere messaggi da questo canale. La cronologia della chat sarà preservata. No comment provided by engineer. @@ -9936,6 +10523,7 @@ Ripetere la richiesta di connessione? Your channel + Il tuo canale No comment provided by engineer. @@ -9988,6 +10576,11 @@ Ripetere la richiesta di connessione? I tuoi contatti resteranno connessi. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Le tue conversazioni appartengono a te, come è sempre stato prima dell'avvento di internet. La rete non è un luogo che visiti. È un luogo che crei e possiedi. E nessuno può portartelo via, che tu lo renda privato o pubblico. + No comment provided by engineer. + Your credentials may be sent unencrypted. Le credenziali potrebbero essere inviate in chiaro. @@ -10008,6 +10601,11 @@ Ripetere la richiesta di connessione? Il tuo gruppo No comment provided by engineer. + + Your network + La tua rete + No comment provided by engineer. + Your preferences Le tue preferenze @@ -10026,6 +10624,8 @@ Ripetere la richiesta di connessione? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + Il tuo profilo **%@** verrà condiviso con i relay e gli iscritti. +I relay hanno accesso ai messaggi del canale. No comment provided by engineer. @@ -10048,6 +10648,11 @@ Relays can access channel messages. Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti. alert message + + Your public address + Il tuo indirizzo pubblico + No comment provided by engineer. + Your random profile Il tuo profilo casuale @@ -10055,10 +10660,12 @@ Relays can access channel messages. Your relay address + L'indirizzo del tuo relay No comment provided by engineer. Your relay name + Il nome del tuo relay No comment provided by engineer. @@ -10076,21 +10683,11 @@ Relays can access channel messages. Le tue impostazioni No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribuisci](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Inviaci un'email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_corsivo_ @@ -10108,6 +10705,7 @@ Relays can access channel messages. accepted + accettato No comment provided by engineer. @@ -10132,6 +10730,7 @@ Relays can access channel messages. active + attivo No comment provided by engineer. @@ -10245,6 +10844,11 @@ marked deleted chat item preview text chiamata… call status + + can't broadcast + impossibile trasmettere + No comment provided by engineer. + can't send messages impossibile inviare messaggi @@ -10282,10 +10886,12 @@ marked deleted chat item preview text channel + canale shown as sender role for channel messages channel profile updated + profilo del canale aggiornato snd group event chat item @@ -10436,6 +11042,7 @@ pref value deleted channel + canale eliminato rcv group event chat item @@ -10550,6 +11157,7 @@ pref value error: %@ + errore: %@ receive error chat item @@ -10684,6 +11292,7 @@ pref value link + link No comment provided by engineer. @@ -10758,6 +11367,7 @@ pref value new + nuovo No comment provided by engineer. @@ -10885,6 +11495,7 @@ time to disappear relay + relay member role @@ -10899,8 +11510,14 @@ time to disappear removed (%d attempts) + rimosso (%d tentativi) receive error chat item + + removed by operator + rimosso da un operatore + No comment provided by engineer. + removed contact address indirizzo di contatto rimosso @@ -11057,6 +11674,7 @@ ultimo msg ricevuto: %2$@ updated channel profile + profilo del canale aggiornato rcv group event chat item @@ -11081,6 +11699,7 @@ ultimo msg ricevuto: %2$@ via %@ + via %@ relay hostname @@ -11160,6 +11779,7 @@ ultimo msg ricevuto: %2$@ you are subscriber + sei iscritto/a No comment provided by engineer. @@ -11222,6 +11842,11 @@ ultimo msg ricevuto: %2$@ \~barrato~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Verifica della firma fallita: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index a95203da15..0d3a7a9088 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -185,9 +185,20 @@ %d 月 time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -348,11 +375,19 @@ channel relay bar progress with errors %u 件のメッセージがスキップされました。 No comment provided by engineer. + + (from owner) + chat link info line + (new) (新規) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (このデバイス v%@) @@ -445,6 +480,12 @@ channel relay bar progress with errors - などなど! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -543,6 +584,10 @@ time interval その他 No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact 新しい連絡先 @@ -666,9 +711,8 @@ swipe action アクティブな接続 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. - プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -734,6 +778,10 @@ swipe action 追加されたメッセージサーバー No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -844,6 +892,14 @@ swipe action すべてのプロフィール profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -901,6 +957,10 @@ swipe action 送信相手も永久メッセージ削除を許可する時のみに許可する。(24時間) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. 連絡先が許可している場合にのみ、メッセージへのリアクションを許可します。 @@ -916,6 +976,10 @@ swipe action メンバーへのダイレクトメッセージを許可する。 No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. 消えるメッセージの送信を許可する。 @@ -926,6 +990,10 @@ swipe action 共有を許可 No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) 送信済みメッセージの永久削除を許可する。(24時間) @@ -1029,11 +1097,6 @@ swipe action 通話に応答 No comment provided by engineer. - - Anybody can host servers. - プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 - No comment provided by engineer. - App build: %@ アプリのビルド: %@ @@ -1227,6 +1290,19 @@ swipe action メッセージのハッシュ値問題 No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls No comment provided by engineer. @@ -1353,6 +1429,10 @@ swipe action あなたと連絡相手が音声メッセージを送信できます。 No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1364,7 +1444,7 @@ swipe action Business address - No comment provided by engineer. + chat link info line Business chats @@ -1383,12 +1463,6 @@ swipe action チャット プロファイル経由 (デフォルト) または [接続経由](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! 通話は既に終了してます! @@ -1540,12 +1614,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1560,6 +1643,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1572,6 +1659,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat チャット @@ -1681,7 +1772,8 @@ set passcode view Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1696,10 +1788,22 @@ set passcode view チャット No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. 20分おきにメッセージを確認する。 @@ -1853,10 +1957,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm 確認 @@ -1949,6 +2049,10 @@ This is your own one-time link! リンク経由で接続 new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link ワンタイムリンクで接続 @@ -2025,7 +2129,7 @@ This is your own one-time link! Connection error (AUTH) 接続エラー (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2075,6 +2179,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows 連絡先の許可 @@ -2140,6 +2248,11 @@ This is your own one-time link! 続ける No comment provided by engineer. + + Contribute + 貢献する + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2166,11 +2279,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - 作成 - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2234,11 +2342,19 @@ This is your own one-time link! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile プロフィールを作成する No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2413,11 +2529,6 @@ This is your own one-time link! 配信のデバッグ No comment provided by engineer. - - Decentralized - 分散型 - No comment provided by engineer. - Decode link relay test step @@ -2787,6 +2898,14 @@ alert button このグループではメンバー間のダイレクトメッセージが使用禁止です。 No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) 無効にする(設定の優先を維持) @@ -2884,6 +3003,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -2972,6 +3095,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit 編集する @@ -2993,7 +3120,7 @@ chat item action Enable 有効 - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3027,6 +3154,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -3045,16 +3176,15 @@ chat item action 即時通知を有効にしますか? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock ロックモード No comment provided by engineer. - - Enable notifications - 通知を有効化 - No comment provided by engineer. - Enable periodic notifications? 定期的な通知を有効にしますか? @@ -3181,6 +3311,10 @@ chat item action 上にパスワードを入力すると表示されます! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3211,7 +3345,7 @@ chat item action Error エラー - No comment provided by engineer. + conn error description Error aborting address change @@ -3523,6 +3657,10 @@ chat item action Error setting delivery receipts! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat チャット開始にエラー発生 @@ -3856,6 +3994,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -3986,6 +4128,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4040,7 +4186,7 @@ Error: %2$@ Group link グループのリンク - No comment provided by engineer. + chat link info line Group links @@ -4148,6 +4294,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works SimpleX の仕組み @@ -4241,11 +4391,6 @@ Error: %2$@ 即座に No comment provided by engineer. - - Immune to spam - スパムや悪質送信を防止 - No comment provided by engineer. - Import 読み込む @@ -4376,9 +4521,9 @@ More improvements are coming soon! 初期の役割 No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + インストール ターミナル用SimpleX Chat No comment provided by engineer. @@ -4429,7 +4574,7 @@ More improvements are coming soon! Invalid connection link 無効な接続リンク - No comment provided by engineer. + conn error description Invalid display name! @@ -4488,6 +4633,10 @@ More improvements are coming soon! メンバーを招待する No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4675,6 +4824,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat SimpleXチャットで会話しよう @@ -4694,6 +4847,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -4861,6 +5018,10 @@ This is your link for group %@! グループメンバーはメッセージへのリアクションを追加できます。 No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) グループのメンバーがメッセージを完全削除することができます。(24時間) @@ -5006,6 +5167,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5032,13 +5201,12 @@ This is your link for group %@! メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。 No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device - 別の端末から移行 + + Migrate device No comment provided by engineer. @@ -5152,6 +5320,10 @@ This is your link for group %@! ネットワークとサーバ No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection No comment provided by engineer. @@ -5160,6 +5332,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5172,6 +5348,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings ネットワーク設定 @@ -5186,6 +5367,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode 新しいパスコード @@ -5276,6 +5461,15 @@ This is your link for group %@! いいえ No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password アプリのパスワードはありません @@ -5415,9 +5609,16 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5473,7 +5674,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5492,11 +5693,19 @@ new chat action 古いデータベース No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link 使い捨ての招待リンク No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5516,6 +5725,10 @@ VPN を有効にする必要があります。 オニオンのホストが使われません。 No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5613,7 +5826,8 @@ VPN を有効にする必要があります。 Open 開く - alert action + alert action +alert button Open Settings @@ -5646,6 +5860,10 @@ VPN を有効にする必要があります。 Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -5702,6 +5920,13 @@ VPN を有効にする必要があります。 Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -5718,6 +5943,10 @@ VPN を有効にする必要があります。 Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5726,6 +5955,10 @@ VPN を有効にする必要があります。 Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5747,6 +5980,10 @@ VPN を有効にする必要があります。 Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING回数 @@ -5800,6 +6037,10 @@ VPN を有効にする必要があります。 画像の貼り付け No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5977,13 +6218,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - プライバシーの基準を新境地に + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -6047,9 +6287,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - 連絡先にプロフィール更新のお知らせが届きます。 + + Profile update will be sent to your SimpleX contacts. alert message @@ -6057,6 +6296,10 @@ Error: %@ 音声/ビデオ通話を禁止する 。 No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. メッセージの完全削除を使用禁止にする。 @@ -6085,6 +6328,10 @@ Error: %@ メンバー間のダイレクトメッセージを使用禁止にする。 No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. 消えるメッセージを使用禁止にする。 @@ -6145,6 +6392,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications プッシュ通知 @@ -6182,23 +6433,14 @@ Enable in *Network & servers* settings. 続きを読む No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + 詳しくはユーザーガイドをご覧ください。 No comment provided by engineer. - - 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)をご覧ください。 - 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)をご覧ください。 + + Read more in our GitHub repository. + 詳しくはGitHubリポジトリをご覧ください。 No comment provided by engineer. @@ -6358,6 +6600,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. 中継サーバーは必要な場合にのみ使用されます。 別の当事者があなたの IP アドレスを監視できます。 @@ -6372,6 +6618,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove 削除 @@ -6624,6 +6874,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6665,6 +6919,10 @@ chat item action 保存して、グループのメンバーにに知らせる No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6843,6 +7101,10 @@ chat item action セキュリティコード No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select 選択 @@ -6960,6 +7222,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. ギャラリーまたはカスタム キーボードから送信します。 @@ -6969,6 +7235,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -6983,6 +7253,10 @@ chat item action 送信元が繋がりリクエストを削除したかもしれません。 No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. No comment provided by engineer. @@ -7210,6 +7484,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -7242,11 +7524,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - アドレスを連絡先と共有しますか? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7280,9 +7565,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - 連絡先と共有する + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7506,6 +7794,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + GitHub でスターを付ける + No comment provided by engineer. + Start chat チャットを開始する @@ -7602,6 +7895,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7610,6 +7907,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -7687,6 +8020,10 @@ Relay address was used to set up this relay for the channel. 写真を撮影 No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7699,10 +8036,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7735,6 +8068,10 @@ Relay address was used to set up this relay for the channel. タップしてシークレットモードで参加 No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7828,6 +8165,10 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -7852,9 +8193,9 @@ It can happen because of some bug or when the connection is compromised.暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります! No comment provided by engineer. - - The future of messaging - 次世代のプライバシー・メッセンジャー + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -7889,6 +8230,10 @@ It can happen because of some bug or when the connection is compromised.古いデータベースは移行時に削除されなかったので、削除することができます。 No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -7928,6 +8273,14 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -8037,6 +8390,10 @@ It can happen because of some bug or when the connection is compromised.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection 新規に接続する場合 @@ -8115,10 +8472,6 @@ You will be prompted to complete authentication before this feature is enabled.< エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。 No comment provided by engineer. - - Toggle chat list: - No comment provided by engineer. - Toggle incognito when connecting. No comment provided by engineer. @@ -8131,6 +8484,10 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total No comment provided by engineer. @@ -8286,12 +8643,16 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update 更新 @@ -8400,11 +8761,6 @@ To connect, please ask your contact to create another connection link and check Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - チャット - No comment provided by engineer. - Use current profile 現在のプロファイルを使用する @@ -8478,6 +8834,10 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8616,6 +8976,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8652,6 +9016,10 @@ To connect, please ask your contact to create another connection link and check 警告: 一部のデータが失われる可能性があります! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICEサーバ @@ -8698,6 +9066,10 @@ To connect, please ask your contact to create another connection link and check 連絡相手にシークレットモードのプロフィールを共有すると、その連絡相手に招待されたグループでも同じプロフィールが使われます。 No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -8929,6 +9301,12 @@ Repeat join request? メッセージを送信できませんでした! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -8938,11 +9316,6 @@ Repeat join request? 確認できませんでした。 もう一度お試しください。 No comment provided by engineer. - - You decide who can connect. - あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9004,6 +9377,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9134,6 +9511,10 @@ Repeat connection request? 連絡先は接続されたままになります。 No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9152,6 +9533,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences あなたの設定 @@ -9190,6 +9575,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile あなたのランダム・プロフィール @@ -9217,21 +9606,11 @@ Relays can access channel messages. あなたの設定 No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [貢献する](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [メールを送信](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [GitHub でスターを付ける](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_斜体_ @@ -9372,6 +9751,10 @@ marked deleted chat item preview text 発信中… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -9998,6 +10381,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10288,6 +10675,10 @@ last received msg: %2$@ \~取り消し線~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 60d888e309..3bf4a6f197 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -185,9 +185,20 @@ %d maanden time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u berichten zijn overgeslagen. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nieuw) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (dit apparaat v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - en meer! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Nog een paar dingen No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Een nieuw contact @@ -669,9 +714,8 @@ swipe action Actieve verbindingen 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. - Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -738,6 +782,10 @@ swipe action Berichtservers toegevoegd No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Extra accent @@ -857,6 +905,14 @@ swipe action Alle profielen profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Alle rapporten worden voor u gearchiveerd. @@ -916,6 +972,10 @@ swipe action Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Sta bericht reacties alleen toe als uw contact dit toestaat. @@ -931,6 +991,10 @@ swipe action Sta het verzenden van directe berichten naar leden toe. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Toestaan dat verdwijnende berichten worden verzonden. @@ -941,6 +1005,10 @@ swipe action Delen toestaan No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Sta toe om verzonden berichten definitief te verwijderen. (24 uur) @@ -1045,11 +1113,6 @@ swipe action Beantwoord oproep No comment provided by engineer. - - Anybody can host servers. - Iedereen kan servers hosten. - No comment provided by engineer. - App build: %@ App build: %@ @@ -1254,6 +1317,19 @@ swipe action Onjuiste bericht hash No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Betere gesprekken @@ -1399,6 +1475,10 @@ swipe action Zowel jij als je contact kunnen spraak berichten verzenden. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1411,7 +1491,7 @@ swipe action Business address Zakelijk adres - No comment provided by engineer. + chat link info line Business chats @@ -1432,15 +1512,6 @@ swipe action Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Door SimpleX Chat te gebruiken, gaat u ermee akkoord: -- alleen legale content te versturen in openbare groepen. -- andere gebruikers te respecteren – geen spam. - No comment provided by engineer. - Call already ended! Oproep al beëindigd! @@ -1600,12 +1671,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1620,6 +1700,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1632,6 +1716,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Chat @@ -1751,7 +1839,8 @@ set passcode view Chat with admins Chat met beheerders - chat toolbar + chat feature +chat toolbar Chat with member @@ -1767,11 +1856,23 @@ set passcode view Chats No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chats met leden No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Controleer uw berichten elke 20 minuten. @@ -1939,11 +2040,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Serveroperators configureren - No comment provided by engineer. - Confirm Bevestigen @@ -2048,6 +2144,10 @@ Dit is uw eigen eenmalige link! Maak verbinding via link new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Verbinden via een eenmalige link? @@ -2126,7 +2226,7 @@ Dit is uw eigen eenmalige link! Connection error (AUTH) Verbindingsfout (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2184,6 +2284,10 @@ Dit is uw eigen eenmalige link! Verbindingen No comment provided by engineer. + + Contact address + chat link info line + Contact allows Contact maakt het mogelijk @@ -2253,6 +2357,11 @@ Dit is uw eigen eenmalige link! Doorgaan No comment provided by engineer. + + Contribute + Bijdragen + No comment provided by engineer. + Conversation deleted! Gesprek verwijderd! @@ -2283,11 +2392,6 @@ Dit is uw eigen eenmalige link! Juiste naam voor %@? alert message - - Create - Maak - No comment provided by engineer. - Create 1-time link Eenmalige link maken @@ -2355,11 +2459,19 @@ Dit is uw eigen eenmalige link! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Maak je profiel aan No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Gemaakt @@ -2542,11 +2654,6 @@ Dit is uw eigen eenmalige link! Foutopsporing bezorging No comment provided by engineer. - - Decentralized - Gedecentraliseerd - No comment provided by engineer. - Decode link relay test step @@ -2941,6 +3048,14 @@ alert button Directe berichten tussen leden zijn niet toegestaan. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Uitschakelen (overschrijvingen behouden) @@ -3046,6 +3161,10 @@ alert button Stuur geen geschiedenis naar nieuwe leden. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Gebruik geen inloggegevens met proxy. @@ -3147,6 +3266,10 @@ chat item action E2E versleutelde meldingen. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Bewerk @@ -3168,7 +3291,7 @@ chat item action Enable Inschakelen - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3204,6 +3327,10 @@ chat item action Schakel cameratoegang in No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -3223,16 +3350,15 @@ chat item action Onmiddellijke meldingen inschakelen? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Vergrendeling inschakelen No comment provided by engineer. - - Enable notifications - Meldingen aanzetten - No comment provided by engineer. - Enable periodic notifications? Periodieke meldingen inschakelen? @@ -3367,6 +3493,10 @@ chat item action Voer hier boven het wachtwoord in om weer te geven! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3399,7 +3529,7 @@ chat item action Error Fout - No comment provided by engineer. + conn error description Error aborting address change @@ -3738,6 +3868,10 @@ chat item action Fout bij het instellen van ontvangst bevestiging! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Fout bij het starten van de chat @@ -4100,6 +4234,10 @@ server test error Voor alle moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Voor chatprofiel %@: @@ -4254,6 +4392,10 @@ Fout: %2$@ Ontvang een melding als u vermeld wordt. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Goedemiddag! @@ -4312,7 +4454,7 @@ Fout: %2$@ Group link Groep link - No comment provided by engineer. + chat link info line Group links @@ -4423,6 +4565,10 @@ Fout: %2$@ Geschiedenis wordt niet naar nieuwe leden gestuurd. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Hoe SimpleX werkt @@ -4521,11 +4667,6 @@ Fout: %2$@ Onmiddellijk No comment provided by engineer. - - Immune to spam - Immuun voor spam en misbruik - No comment provided by engineer. - Import Importeren @@ -4668,9 +4809,9 @@ Binnenkort meer verbeteringen! Initiële rol No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installeer SimpleX Chat voor terminal No comment provided by engineer. @@ -4728,7 +4869,7 @@ Binnenkort meer verbeteringen! Invalid connection link Ongeldige verbinding link - No comment provided by engineer. + conn error description Invalid display name! @@ -4792,6 +4933,10 @@ Binnenkort meer verbeteringen! Nodig leden uit No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Uitnodigen voor een chat @@ -4990,6 +5135,10 @@ Dit is jouw link voor groep %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Laten we praten in SimpleX Chat @@ -5010,6 +5159,10 @@ Dit is jouw link voor groep %@! Koppel mobiele en desktop-apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Gekoppelde desktop opties @@ -5190,6 +5343,10 @@ Dit is jouw link voor groep %@! Groepsleden kunnen bericht reacties toevoegen. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) @@ -5352,6 +5509,14 @@ Dit is jouw link voor groep %@! Berichten van %@ worden getoond! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Berichten in deze chat zullen nooit worden verwijderd. @@ -5382,16 +5547,15 @@ Dit is jouw link voor groep %@! Berichten, bestanden en oproepen worden beschermd door **kwantumbestendige e2e encryptie** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Apparaat migreren No comment provided by engineer. - - Migrate from another device - Migreer vanaf een ander apparaat - No comment provided by engineer. - Migrate here Migreer hierheen @@ -5512,6 +5676,10 @@ Dit is jouw link voor groep %@! Netwerk & servers No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection Netwerkverbinding @@ -5522,6 +5690,10 @@ Dit is jouw link voor groep %@! Netwerk decentralisatie No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden. @@ -5537,6 +5709,11 @@ Dit is jouw link voor groep %@! Netwerkbeheerder No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Netwerk instellingen @@ -5552,6 +5729,10 @@ Dit is jouw link voor groep %@! Nieuw token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nieuwe toegangscode @@ -5650,6 +5831,15 @@ Dit is jouw link voor groep %@! Nee No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Geen app wachtwoord @@ -5807,9 +5997,16 @@ Dit is jouw link voor groep %@! Geen ongelezen chats No comment provided by engineer. - - No user identifiers. - Geen gebruikers-ID's. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5873,7 +6070,7 @@ Dit is jouw link voor groep %@! OK OK - No comment provided by engineer. + alert button Off @@ -5892,11 +6089,19 @@ new chat action Oude database No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Eenmalige uitnodiging link No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5916,6 +6121,10 @@ Vereist het inschakelen van VPN. Onion hosts worden niet gebruikt. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Alleen chateigenaren kunnen voorkeuren wijzigen. @@ -6017,7 +6226,8 @@ Vereist het inschakelen van VPN. Open Open - alert action + alert action +alert button Open Settings @@ -6052,6 +6262,10 @@ Vereist het inschakelen van VPN. Open voorwaarden No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6114,6 +6328,13 @@ Vereist het inschakelen van VPN. Operatorserver alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Of importeer archiefbestand @@ -6134,6 +6355,10 @@ Vereist het inschakelen van VPN. Of deel deze bestands link veilig No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Of laat deze code zien @@ -6144,6 +6369,10 @@ Vereist het inschakelen van VPN. Of om privé te delen No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organiseer chats in lijsten @@ -6169,6 +6398,10 @@ Vereist het inschakelen van VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING count @@ -6224,6 +6457,10 @@ Vereist het inschakelen van VPN. Afbeelding plakken No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Plak een link om te verbinden! @@ -6421,14 +6658,12 @@ Fout: %@ Privacybeleid en gebruiksvoorwaarden. No comment provided by engineer. - - Privacy redefined - Privacy opnieuw gedefinieerd + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. + + Private and secure messaging. No comment provided by engineer. @@ -6499,9 +6734,8 @@ Fout: %@ Profiel thema No comment provided by engineer. - - Profile update will be sent to your contacts. - Profiel update wordt naar uw contacten verzonden. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6509,6 +6743,10 @@ Fout: %@ Audio/video gesprekken verbieden. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Verbied het definitief verwijderen van berichten. @@ -6539,6 +6777,10 @@ Fout: %@ Verbied het sturen van directe berichten naar leden. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Verbied het verzenden van verdwijnende berichten. @@ -6605,6 +6847,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxy vereist wachtwoord No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push meldingen @@ -6645,24 +6891,14 @@ Schakel dit in in *Netwerk en servers*-instellingen. Lees meer No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Lees meer in de Gebruikershandleiding. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Lees meer in onze GitHub-repository. No comment provided by engineer. @@ -6838,6 +7074,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien. @@ -6852,6 +7092,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Verwijderen @@ -7131,6 +7375,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Veilig bestanden ontvangen @@ -7175,6 +7423,10 @@ chat item action Opslaan en groep leden melden No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Opslaan en opnieuw verbinden @@ -7367,6 +7619,10 @@ chat item action Beveiligingscode No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Selecteer @@ -7494,6 +7750,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Stuur ze vanuit de galerij of aangepaste toetsenborden. @@ -7504,6 +7764,10 @@ chat item action Stuur tot 100 laatste berichten naar nieuwe leden. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7518,6 +7782,10 @@ chat item action De afzender heeft mogelijk het verbindingsverzoek verwijderd. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Het verzenden van ontvangst bevestiging wordt ingeschakeld voor alle contacten in alle zichtbare chatprofielen. @@ -7776,6 +8044,14 @@ chat item action Instellingen zijn gewijzigd. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Vorm profiel afbeeldingen @@ -7812,11 +8088,14 @@ chat item action Adres openbaar delen No comment provided by engineer. - - Share address with contacts? - Adres delen met contacten? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Delen vanuit andere apps. @@ -7854,9 +8133,12 @@ chat item action Delen op SimpleX No comment provided by engineer. - - Share with contacts - Delen met contacten + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8103,6 +8385,11 @@ report reason Vierkant, cirkel of iets daartussenin. No comment provided by engineer. + + Star on GitHub + Star on GitHub + No comment provided by engineer. + Start chat Begin gesprek @@ -8207,6 +8494,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8215,6 +8506,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8299,6 +8626,10 @@ Relay address was used to set up this relay for the channel. Foto nemen No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -8311,11 +8642,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tik op SimpleX-adres maken in het menu om het later te maken. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8349,6 +8675,10 @@ Relay address was used to set up this relay for the channel. Tik hier om incognito lid te worden No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Tik hier om de link te plakken @@ -8449,6 +8779,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De code die u heeft gescand is geen SimpleX link QR-code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. De verbinding heeft de limiet van niet-afgeleverde berichten bereikt. Uw contactpersoon is mogelijk offline. @@ -8474,9 +8808,9 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten! No comment provided by engineer. - - The future of messaging - De volgende generatie privéberichten + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8513,6 +8847,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Dezelfde voorwaarden gelden voor operator **%@**. @@ -8558,6 +8896,14 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Thema's No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Deze voorwaarden zijn ook van toepassing op: **%@**. @@ -8679,6 +9025,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Om ongewenste berichten te verbergen. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Om een nieuwe verbinding te maken @@ -8764,11 +9114,6 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contact te verifiëren. No comment provided by engineer. - - Toggle chat list: - Chatlijst wisselen: - No comment provided by engineer. - Toggle incognito when connecting. Schakel incognito in tijdens het verbinden. @@ -8784,6 +9129,10 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc De transparantie van de werkbalk No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total Totaal @@ -8952,13 +9301,17 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Unsupported connection link Niet-ondersteunde verbindingslink - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Update @@ -9078,11 +9431,6 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik TCP-poort 443 alleen voor vooraf ingestelde servers. No comment provided by engineer. - - Use chat - Gebruik chat - No comment provided by engineer. - Use current profile Gebruik het huidige profiel @@ -9165,6 +9513,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik de app met één hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Gebruik een webpoort @@ -9316,6 +9668,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Wachten op desktop... @@ -9356,6 +9712,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Waarschuwing: u kunt sommige gegevens verliezen! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE servers @@ -9405,6 +9765,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi Wifi @@ -9664,6 +10028,12 @@ Deelnameverzoek herhalen? Je kunt geen berichten versturen! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9673,11 +10043,6 @@ Deelnameverzoek herhalen? U kon niet worden geverifieerd; probeer het opnieuw. No comment provided by engineer. - - You decide who can connect. - Jij bepaalt wie er verbinding mag maken. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9745,6 +10110,10 @@ Verbindingsverzoek herhalen? U zou meldingen moeten ontvangen. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9879,6 +10248,10 @@ Verbindingsverzoek herhalen? Uw contacten blijven verbonden. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Uw inloggegevens worden mogelijk niet-versleuteld verzonden. @@ -9898,6 +10271,10 @@ Verbindingsverzoek herhalen? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Jouw voorkeuren @@ -9938,6 +10315,10 @@ Relays can access channel messages. Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. alert message + + Your public address + No comment provided by engineer. + Your random profile Je willekeurige profiel @@ -9966,21 +10347,11 @@ Relays can access channel messages. Uw instellingen No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Bijdragen](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Stuur ons een e-mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_cursief_ @@ -10135,6 +10506,10 @@ marked deleted chat item preview text bellen… call status + + can't broadcast + No comment provided by engineer. + can't send messages kan geen berichten versturen @@ -10787,6 +11162,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address contactadres verwijderd @@ -11105,6 +11484,10 @@ laatst ontvangen bericht: %2$@ \~staking~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 57d22dd63b..b232aa84af 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -185,9 +185,20 @@ %d miesięcy time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u pominiętych wiadomości. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nowy) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (to urządzenie v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - i więcej! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Jeszcze kilka rzeczy No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Nowy kontakt @@ -670,9 +715,8 @@ swipe action Aktywne połączenia 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. - Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Dodano serwery wiadomości No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Dodatkowy akcent @@ -860,6 +908,14 @@ swipe action Wszystkie profile profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Wszystkie raporty zostaną dla Ciebie zarchiwizowane. @@ -920,6 +976,10 @@ swipe action Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Zezwalaj na reakcje wiadomości tylko wtedy, gdy zezwala na to Twój kontakt. @@ -935,6 +995,10 @@ swipe action Zezwalaj na wysyłanie bezpośrednich wiadomości do członków. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Zezwól na wysyłanie znikających wiadomości. @@ -945,6 +1009,10 @@ swipe action Zezwól na udostępnianie No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Zezwól na nieodwracalne usunięcie wysłanych wiadomości. (24 godziny) @@ -1050,11 +1118,6 @@ swipe action Odbierz połączenie No comment provided by engineer. - - Anybody can host servers. - Każdy może hostować serwery. - No comment provided by engineer. - App build: %@ Kompilacja aplikacji: %@ @@ -1260,6 +1323,21 @@ swipe action Zły hash wiadomości No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Ciesz się swobodą w swojej sieci. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana. + No comment provided by engineer. + Better calls Lepsze połączenia @@ -1409,6 +1487,10 @@ swipe action Zarówno Ty, jak i Twój kontakt możecie wysyłać wiadomości głosowe. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1421,7 +1503,7 @@ swipe action Business address Adres firmowy - No comment provided by engineer. + chat link info line Business chats @@ -1443,15 +1525,6 @@ swipe action Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Korzystając z SimpleX Chat, zgadzasz się: -- wysyłać tylko legalne treści w grupach publicznych. -- szanować innych użytkowników – nie spamować. - No comment provided by engineer. - Call already ended! Połączenie już zakończone! @@ -1612,12 +1685,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1714,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1730,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Czat @@ -1763,7 +1853,8 @@ set passcode view Chat with admins Czatuj z administratorami - chat toolbar + chat feature +chat toolbar Chat with member @@ -1780,11 +1871,23 @@ set passcode view Czaty No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Czaty z członkami No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Sprawdzaj wiadomości co 20 min. @@ -1952,11 +2055,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Skonfiguruj operatorów serwerów - No comment provided by engineer. - Confirm Potwierdź @@ -2062,6 +2160,10 @@ To jest twój jednorazowy link! Połącz się przez link new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Połącz przez jednorazowy link @@ -2140,7 +2242,7 @@ To jest twój jednorazowy link! Connection error (AUTH) Błąd połączenia (UWIERZYTELNIANIE) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2301,10 @@ To jest twój jednorazowy link! Połączenia No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kontakt pozwala @@ -2269,6 +2375,11 @@ To jest twój jednorazowy link! Kontynuuj No comment provided by engineer. + + Contribute + Przyczyń się + No comment provided by engineer. + Conversation deleted! Rozmowa usunięta! @@ -2299,11 +2410,6 @@ To jest twój jednorazowy link! Poprawić imię na %@? alert message - - Create - Utwórz - No comment provided by engineer. - Create 1-time link Utwórz jednorazowy link @@ -2372,11 +2478,19 @@ To jest twój jednorazowy link! Utwórz swój adres No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Utwórz swój profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Utworzono @@ -2559,11 +2673,6 @@ To jest twój jednorazowy link! Dostarczenie debugowania No comment provided by engineer. - - Decentralized - Zdecentralizowane - No comment provided by engineer. - Decode link relay test step @@ -2962,6 +3071,14 @@ alert button Bezpośrednie wiadomości między członkami są zabronione w tej grupie. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Wyłącz (zachowaj nadpisania) @@ -3067,6 +3184,10 @@ alert button Nie wysyłaj historii do nowych członków. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Nie używaj danych logowania do proxy. @@ -3168,6 +3289,10 @@ chat item action Powiadomienia szyfrowane E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Edytuj @@ -3190,7 +3315,7 @@ chat item action Enable Włącz - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3226,6 +3351,10 @@ chat item action Włącz dostęp do kamery No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Włącz domyślnie znikające wiadomości. @@ -3246,16 +3375,15 @@ chat item action Włączyć natychmiastowe powiadomienia? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Włącz blokadę No comment provided by engineer. - - Enable notifications - Włącz powiadomienia - No comment provided by engineer. - Enable periodic notifications? Włączyć okresowe powiadomienia? @@ -3390,6 +3518,10 @@ chat item action Wprowadź hasło powyżej, aby pokazać! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3422,7 +3554,7 @@ chat item action Error Błąd - No comment provided by engineer. + conn error description Error aborting address change @@ -3766,6 +3898,10 @@ chat item action Błąd ustawiania potwierdzeń dostawy! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Błąd uruchamiania czatu @@ -4134,6 +4270,10 @@ server test error Dla wszystkich moderatorów No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Dla profilu czatu %@: @@ -4288,6 +4428,10 @@ Błąd: %2$@ Otrzymuj powiadomienia, gdy ktoś wspomni o Tobie. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Dzień dobry! @@ -4346,7 +4490,7 @@ Błąd: %2$@ Group link Link do grupy - No comment provided by engineer. + chat link info line Group links @@ -4458,6 +4602,10 @@ Błąd: %2$@ Historia nie jest wysyłana do nowych członków. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Jak działa SimpleX @@ -4558,11 +4706,6 @@ Błąd: %2$@ Natychmiast No comment provided by engineer. - - Immune to spam - Odporność na spam i nadużycia - No comment provided by engineer. - Import Importuj @@ -4705,9 +4848,9 @@ Wkrótce pojawią się kolejne ulepszenia! Rola początkowa No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Zainstaluj SimpleX Chat na terminal No comment provided by engineer. @@ -4765,7 +4908,7 @@ Wkrótce pojawią się kolejne ulepszenia! Invalid connection link Nieprawidłowy link połączenia - No comment provided by engineer. + conn error description Invalid display name! @@ -4830,6 +4973,10 @@ Wkrótce pojawią się kolejne ulepszenia! Zaproś członków No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Zaproś do czatu @@ -5030,6 +5177,10 @@ To jest twój link do grupy %@! Mniejszy ruch w sieciach komórkowych. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Porozmawiajmy w SimpleX Chat @@ -5050,6 +5201,10 @@ To jest twój link do grupy %@! Połącz mobile i komputerowe aplikacje! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Połączone opcje komputera @@ -5235,6 +5390,10 @@ To jest twój link do grupy %@! Członkowie grupy mogą dodawać reakcje wiadomości. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny) @@ -5399,6 +5558,14 @@ To jest twój link do grupy %@! Wiadomości od %@ zostaną pokazane! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Wiadomości na tym czacie nigdy nie zostaną usunięte. @@ -5429,16 +5596,15 @@ To jest twój link do grupy %@! Wiadomości, pliki i połączenia są chronione przez **kwantowo odporne szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Zmigruj urządzenie No comment provided by engineer. - - Migrate from another device - Zmigruj z innego urządzenia - No comment provided by engineer. - Migrate here Zmigruj tutaj @@ -5559,6 +5725,10 @@ To jest twój link do grupy %@! Sieć i serwery No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection Połączenie z siecią @@ -5569,6 +5739,10 @@ To jest twój link do grupy %@! Decentralizacja sieci No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej. @@ -5584,6 +5758,11 @@ To jest twój link do grupy %@! Operator sieci No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Ustawienia sieci @@ -5599,6 +5778,10 @@ To jest twój link do grupy %@! Nowy token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nowy Pin @@ -5698,6 +5881,15 @@ To jest twój link do grupy %@! Nie No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Brak hasła aplikacji @@ -5856,9 +6048,18 @@ To jest twój link do grupy %@! Brak nieprzeczytanych czatów No comment provided by engineer. - - No user identifiers. - Brak identyfikatorów użytkownika. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem. No comment provided by engineer. @@ -5922,7 +6123,7 @@ To jest twój link do grupy %@! OK OK - No comment provided by engineer. + alert button Off @@ -5941,11 +6142,19 @@ new chat action Stara baza danych No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Jednorazowy link zaproszenia No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6174,10 @@ Wymaga włączenia VPN. Hosty onion nie będą używane. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Tylko właściciele czatu mogą zmieniać preferencje. @@ -6068,7 +6281,8 @@ Wymaga włączenia VPN. Open Otwórz - alert action + alert action +alert button Open Settings @@ -6104,6 +6318,10 @@ Wymaga włączenia VPN. Otwórz warunki No comment provided by engineer. + + Open external link? + alert title + Open full link Otwórz pełny link @@ -6173,6 +6391,13 @@ Wymaga włączenia VPN. Serwer Operatora alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Lub zaimportuj plik archiwalny @@ -6193,6 +6418,10 @@ Wymaga włączenia VPN. Lub bezpiecznie udostępnij ten link pliku No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Lub pokaż ten kod @@ -6203,6 +6432,10 @@ Wymaga włączenia VPN. Lub udostępnij prywatnie No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organizuj czaty jako listy @@ -6228,6 +6461,10 @@ Wymaga włączenia VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Liczba PINGÓW @@ -6283,6 +6520,10 @@ Wymaga włączenia VPN. Wklej obraz No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Wklej link, aby połączyć! @@ -6480,14 +6721,12 @@ Błąd: %@ Polityka prywatności i warunki korzystania. No comment provided by engineer. - - Privacy redefined - Redefinicja prywatności + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów. + + Private and secure messaging. No comment provided by engineer. @@ -6559,9 +6798,8 @@ Błąd: %@ Motyw profilu No comment provided by engineer. - - Profile update will be sent to your contacts. - Aktualizacja profilu zostanie wysłana do Twoich kontaktów. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6569,6 +6807,10 @@ Błąd: %@ Zabroń połączeń audio/wideo. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Zabroń nieodwracalnego usuwania wiadomości. @@ -6599,6 +6841,10 @@ Błąd: %@ Zabroń wysyłania bezpośrednich wiadomości do członków. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Zabroń wysyłania znikających wiadomości. @@ -6666,6 +6912,10 @@ Włącz w ustawianiach *Sieć i serwery* . Proxy wymaga hasła No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Powiadomienia push @@ -6706,24 +6956,14 @@ Włącz w ustawianiach *Sieć i serwery* . Przeczytaj więcej No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Przeczytaj więcej w Poradniku Użytkownika. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Przeczytaj więcej w [Podręczniku Użytkownika](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). - Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Przeczytaj więcej na naszym repozytorium GitHub. No comment provided by engineer. @@ -6899,6 +7139,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Serwer przekaźnikowy jest używany tylko w razie potrzeby. Inna strona może obserwować Twój adres IP. @@ -6913,6 +7157,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Usuń @@ -7196,6 +7444,10 @@ swipe action Proxy SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Bezpiecznie otrzymuj pliki @@ -7241,6 +7493,10 @@ chat item action Zapisz i powiadom członków grupy No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Zapisz i połącz ponownie @@ -7439,6 +7695,10 @@ chat item action Kod bezpieczeństwa No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Wybierz @@ -7569,6 +7829,10 @@ chat item action Wyślij prośbę bez wiadomości No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Wyślij je z galerii lub niestandardowych klawiatur. @@ -7579,6 +7843,10 @@ chat item action Wysyłaj do 100 ostatnich wiadomości do nowych członków. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Wyślij swoją prywatną opinię do grup. @@ -7594,6 +7862,10 @@ chat item action Nadawca mógł usunąć prośbę o połączenie. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Wysyłanie potwierdzeń dostawy zostanie włączone dla wszystkich kontaktów we wszystkich widocznych profilach czatu. @@ -7853,6 +8125,14 @@ chat item action Ustawienia zostały zmienione. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Kształtuj obrazy profilowe @@ -7889,11 +8169,14 @@ chat item action Udostępnij adres publicznie No comment provided by engineer. - - Share address with contacts? - Udostępnić adres kontaktom? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Udostępnij z innych aplikacji. @@ -7933,9 +8216,12 @@ chat item action Udostępnij do SimpleX No comment provided by engineer. - - Share with contacts - Udostępnij kontaktom + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8185,6 +8471,11 @@ report reason Kwadrat, okrąg lub cokolwiek pomiędzy. No comment provided by engineer. + + Star on GitHub + Daj gwiazdkę na GitHub + No comment provided by engineer. + Start chat Rozpocznij czat @@ -8289,6 +8580,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8297,6 +8592,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8382,6 +8713,10 @@ Relay address was used to set up this relay for the channel. Zrób zdjęcie No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Dotknij Połącz aby rozpocząć czat @@ -8397,11 +8732,6 @@ Relay address was used to set up this relay for the channel. Dotknij Połącz aby użyć bota No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Dotknij Stwórz adres SimpleX w menu aby utworzyć go później. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8436,6 +8766,10 @@ Relay address was used to set up this relay for the channel. Dotnij, aby dołączyć w trybie incognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Dotknij, aby wkleić link @@ -8537,6 +8871,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Kod, który zeskanowałeś nie jest kodem QR linku SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Połączenie osiągnęło limit niedostarczonych wiadomości, Twój kontakt może być offline. @@ -8562,9 +8900,9 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu! No comment provided by engineer. - - The future of messaging - Następna generacja prywatnych wiadomości + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8602,6 +8940,11 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Stara baza danych nie została usunięta podczas migracji, można ją usunąć. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Te same warunki będą miały zastosowanie do operatora **%@**. @@ -8647,6 +8990,16 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Motywy No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony. + No comment provided by engineer. + These conditions will also apply for: **%@**. Warunki te będą miały również zastosowanie w przypadku: **%@**. @@ -8770,6 +9123,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aby ukryć niechciane wiadomości. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Aby nawiązać nowe połączenie @@ -8857,11 +9214,6 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach. No comment provided by engineer. - - Toggle chat list: - Przełącz listę czatów: - No comment provided by engineer. - Toggle incognito when connecting. Przełącz incognito przy połączeniu. @@ -8877,6 +9229,10 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Nieprzezroczystość paska narzędzi No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total Łącznie @@ -9046,13 +9402,17 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Unsupported connection link Nieobsługiwane łącze połączenia - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Do nowych członków wysyłanych jest do 100 ostatnich wiadomości. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Aktualizuj @@ -9178,11 +9538,6 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Używaj portu TCP 443 tylko dla domyślnych serwerów. No comment provided by engineer. - - Use chat - Użyj czatu - No comment provided by engineer. - Use current profile Użyj obecnego profilu @@ -9266,6 +9621,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Korzystaj z aplikacji jedną ręką. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Użyj portu internetowego @@ -9418,6 +9777,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Oczekiwanie na komputer... @@ -9458,6 +9821,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Uwaga: możesz stracić niektóre dane! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Serwery WebRTC ICE @@ -9508,6 +9875,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9769,6 +10140,12 @@ Powtórzyć prośbę dołączenia? Nie możesz wysyłać wiadomości! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9778,11 +10155,6 @@ Powtórzyć prośbę dołączenia? Nie można zweryfikować użytkownika; proszę spróbować ponownie. No comment provided by engineer. - - You decide who can connect. - Ty decydujesz, kto może się połączyć. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9850,6 +10222,11 @@ Powtórzyć prośbę połączenia? Powinieneś otrzymywać powiadomienia. token info + + You were born without an account + Urodziłeś się bez konta. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Będziesz mógł wysyłać wiadomości **dopiero po zaakceptowaniu Twojej prośby**. @@ -9988,6 +10365,11 @@ Powtórzyć prośbę połączenia? Twoje kontakty pozostaną połączone. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne. + No comment provided by engineer. + Your credentials may be sent unencrypted. Twoje poświadczenia mogą zostać wysłane niezaszyfrowane. @@ -10008,6 +10390,10 @@ Powtórzyć prośbę połączenia? Twoja grupa No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Twoje preferencje @@ -10048,6 +10434,10 @@ Relays can access channel messages. Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów. alert message + + Your public address + No comment provided by engineer. + Your random profile Twój losowy profil @@ -10076,21 +10466,11 @@ Relays can access channel messages. Twoje ustawienia No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Przyczyń się](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Wyślij do nas email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_kursywa_ @@ -10245,6 +10625,10 @@ marked deleted chat item preview text dzwonie… call status + + can't broadcast + No comment provided by engineer. + can't send messages nie można wysłać wiadomości @@ -10901,6 +11285,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address usunięto adres kontaktu @@ -11222,6 +11610,10 @@ ostatnia otrzymana wiadomość: %2$@ \~strajk~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 64f2546b30..a438327ba1 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -167,7 +167,7 @@ %d hours - %d ч. + %d ч time interval @@ -182,12 +182,26 @@ %d months - %d мес. + %d мес time interval - - %d relays - channel relay bar + + %d relays failed + %d релеев с ошибками + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d релеев неактивны + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d релеев удалены + channel relay bar +channel subscriber relay bar %d sec @@ -206,10 +220,12 @@ %d subscriber + %d подписчик channel subscriber count %d subscribers + %d подписчиков channel subscriber count @@ -219,21 +235,45 @@ %1$d/%2$d relays active + %1$d/%2$d релеев активны channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d релеев активны, %3$d с ошибками + channel relay bar + %1$d/%2$d relays active, %3$d failed + %1$d/%2$d релеев активны, %3$d с ошибками channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d релеев активны, %3$d удалены + channel relay bar %1$d/%2$d relays connected + %1$d/%2$d релеев подключены channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + %1$d/%2$d релеев подключены, %3$d с ошибками + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d релеев подключены, %3$d с ошибками + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d релеев подключены, %3$d удалены + channel subscriber relay bar %lld @@ -247,6 +287,7 @@ channel relay bar progress with errors %lld channel events + %lld событий канала No comment provided by engineer. @@ -276,7 +317,7 @@ channel relay bar progress with errors %lld messages blocked by admin - %lld сообщений заблокировано администратором + %lld сообщений заблокировано админом No comment provided by engineer. @@ -291,7 +332,7 @@ channel relay bar progress with errors %lld minutes - %lld минуты + %lld минут(ы) No comment provided by engineer. @@ -349,11 +390,21 @@ channel relay bar progress with errors %u сообщений пропущено. No comment provided by engineer. + + (from owner) + (от владельца) + chat link info line + (new) (новое) No comment provided by engineer. + + (signed) + (с подписью) + chat link info line + (this device v%@) (это устройство v%@) @@ -386,7 +437,7 @@ channel relay bar progress with errors **Please note**: you will NOT be able to recover or change passphrase if you lose it. - **Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете. + **Внимание**: Вы не сможете восстановить или поменять пароль, если потеряете его. No comment provided by engineer. @@ -401,16 +452,17 @@ channel relay bar progress with errors **Test relay** to retrieve its name. + **Протестируйте релей**, чтобы получить его имя. No comment provided by engineer. **Warning**: Instant push notifications require passphrase saved in Keychain. - **Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain. + **Внимание**: для работы мгновенных уведомлений пароль должен быть сохранён в Keychain. No comment provided by engineer. **Warning**: the archive will be removed. - **Внимание**: архив будет удален. + **Внимание**: архив будет удалён. No comment provided by engineer. @@ -433,7 +485,7 @@ channel relay bar progress with errors - delivery receipts (up to 20 members). - faster and more stable. - соединиться с [каталогом групп](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! -- отчеты о доставке (до 20 членов). +- отчёты о доставке (до 20 членов). - быстрее и стабильнее. No comment provided by engineer. @@ -446,6 +498,15 @@ channel relay bar progress with errors - и прочее! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - включение картинок ссылок. +- защита от фишинга. +- удаление трекинга ссылок. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -541,7 +602,12 @@ time interval A few more things - Еще несколько изменений + Ещё несколько изменений + No comment provided by engineer. + + + A link for one person to connect + Ссылка для одного человека No comment provided by engineer. @@ -562,7 +628,7 @@ time interval A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. - Будет использовано отдельное TCP соединение **для каждого контакта и члена группы**. + Будет использовано отдельное TCP-соединение **для каждого контакта и члена группы**. **Примечание**: Чем больше подключений, тем быстрее разряжается батарея и расходуется трафик, а некоторые соединения могут отваливаться. No comment provided by engineer. @@ -642,7 +708,7 @@ swipe action Accept member - Принять члена + Принять члена группы alert title @@ -670,9 +736,9 @@ swipe action Активные соединения 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. - Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Добавьте адрес в свой профиль, чтобы Ваши SimpleX контакты могли поделиться им. Профиль будет отправлен Вашим SimpleX контактам. No comment provided by engineer. @@ -687,7 +753,7 @@ swipe action Add message - Добавить cообщение + Добавить сообщение placeholder for sending contact request @@ -702,7 +768,7 @@ swipe action Add servers by scanning QR codes. - Добавить серверы через QR код. + Добавить серверы через QR-код. No comment provided by engineer. @@ -740,6 +806,11 @@ swipe action Дополнительные серверы сообщений No comment provided by engineer. + + Adding relays will be supported later. + Добавление релеев будет поддерживаться позже. + No comment provided by engineer. + Additional accent Дополнительный акцент @@ -792,7 +863,7 @@ swipe action Advanced settings - Настройки сети + Дополнительные настройки No comment provided by engineer. @@ -812,7 +883,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. - Все чаты будут удалены из списка %@, и список удален. + Все чаты будут удалены из списка %@, и список удалён. alert message @@ -832,11 +903,12 @@ swipe action All messages + Все сообщения No comment provided by engineer. All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. - Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах. + Все сообщения и файлы отправляются с **сквозным шифрованием**, с пост-квантовой безопасностью в прямых разговорах. No comment provided by engineer. @@ -859,6 +931,16 @@ swipe action Все профили profile dropdown + + All relays failed + Все релеи недоступны + No comment provided by engineer. + + + All relays removed + Все релеи удалены + No comment provided by engineer. + All reports will be archived for you. Все сообщения о нарушениях будут заархивированы для вас. @@ -876,12 +958,12 @@ swipe action All your contacts will remain connected. Profile update will be sent to your contacts. - Все Ваши контакты сохранятся. Обновленный профиль будет отправлен Вашим контактам. + Все Ваши контакты сохранятся. Обновлённый профиль будет отправлен Вашим контактам. No comment provided by engineer. All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. - Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы. + Все ваши контакты, разговоры и файлы будут надёжно зашифрованы и загружены на выбранные XFTP-серверы. No comment provided by engineer. @@ -919,6 +1001,11 @@ swipe action Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа) No comment provided by engineer. + + Allow members to chat with admins. + Разрешить членам группы общаться с админами. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Разрешить реакции на сообщения, только если ваш контакт разрешает их. @@ -934,6 +1021,11 @@ swipe action Разрешить личные сообщения членам группы. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Разрешить отправку личных сообщений подписчикам. + No comment provided by engineer. + Allow sending disappearing messages. Разрешить посылать исчезающие сообщения. @@ -944,6 +1036,11 @@ swipe action Разрешить поделиться No comment provided by engineer. + + Allow subscribers to chat with admins. + Разрешить подписчикам общаться с админами. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Разрешить необратимо удалять отправленные сообщения. (24 часа) @@ -1031,7 +1128,7 @@ swipe action Always use relay - Всегда соединяться через relay + Всегда соединяться через релей No comment provided by engineer. @@ -1049,11 +1146,6 @@ swipe action Принять звонок No comment provided by engineer. - - Anybody can host servers. - Кто угодно может запустить сервер. - No comment provided by engineer. - App build: %@ Сборка приложения: %@ @@ -1086,7 +1178,7 @@ swipe action App passcode is replaced with self-destruct passcode. - Код доступа в приложение будет заменен кодом самоуничтожения. + Код доступа в приложение будет заменён кодом самоуничтожения. No comment provided by engineer. @@ -1186,6 +1278,7 @@ swipe action Audio call + Аудиозвонок No comment provided by engineer. @@ -1220,7 +1313,7 @@ swipe action Auto-accept - Автоприем + Автоприём No comment provided by engineer. @@ -1230,7 +1323,7 @@ swipe action Auto-accept images - Автоприем изображений + Автоприём изображений No comment provided by engineer. @@ -1255,7 +1348,24 @@ swipe action Bad message hash - Ошибка хэш сообщения + Ошибка хэша сообщения + No comment provided by engineer. + + + Be free +in your network + Будь свободен +в своей сети + No comment provided by engineer. + + + Be free in your network. + Будь свободен в своей сети. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять. No comment provided by engineer. @@ -1320,7 +1430,7 @@ swipe action Black - Черная + Чёрная No comment provided by engineer. @@ -1355,6 +1465,7 @@ swipe action Block subscriber for all? + Заблокировать подписчика для всех? No comment provided by engineer. @@ -1407,8 +1518,14 @@ swipe action Вы и Ваш контакт можете отправлять голосовые сообщения. No comment provided by engineer. + + Bottom bar + Нижнее меню + No comment provided by engineer. + Broadcast + Опубликовать compose placeholder for channel owner @@ -1418,8 +1535,8 @@ swipe action Business address - Бизнес адрес - No comment provided by engineer. + Бизнес-адрес + chat link info line Business chats @@ -1428,7 +1545,7 @@ swipe action Business connection - Бизнес контакт + Бизнес-контакт No comment provided by engineer. @@ -1441,18 +1558,9 @@ swipe action По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Используя SimpleX Chat, Вы согласны: -- отправлять только законные сообщения в публичных группах. -- уважать других пользователей – не отправлять спам. - No comment provided by engineer. - Call already ended! - Звонок уже завершен! + Звонок уже завершён! No comment provided by engineer. @@ -1472,7 +1580,7 @@ swipe action Can't call contact - Не удается позвонить контакту + Не удаётся позвонить контакту No comment provided by engineer. @@ -1549,7 +1657,7 @@ new chat action Change chat profiles - Поменять профили + Изменить профили чата authentication reason @@ -1600,48 +1708,80 @@ set passcode view Channel + Канал No comment provided by engineer. Channel display name + Имя канала No comment provided by engineer. Channel full name (optional) + Полное имя канала (необязательно) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + У канала нет активных релеев. Попробуйте подключиться позже. + alert message +alert subtitle + Channel image + Картинка канала No comment provided by engineer. Channel link + Ссылка канала + chat link info line + + + Channel preferences + Предпочтения канала No comment provided by engineer. Channel profile + Профиль канала No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + Профиль канала хранится на устройствах подписчиков и на чат-релеях. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + Профиль канала был изменен. Если Вы сохраните его, обновлённый профиль будет отправлен подписчикам канала. alert message + + Channel temporarily unavailable + Канал временно недоступен + alert title + Channel will be deleted for all subscribers - this cannot be undone! + Канал будет удалён для всех подписчиков - это нельзя отменить! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + Канал будет удалён для Вас - это нельзя отменить! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + Канал начнёт работу с %1$d из %2$d релеев. Продолжить? alert message + + Channels + Каналы + No comment provided by engineer. + Chat Разговор @@ -1699,7 +1839,7 @@ set passcode view Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. - Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите ее обратно до запуска чата. + Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите её обратно до запуска чата. No comment provided by engineer. @@ -1714,7 +1854,7 @@ set passcode view Chat preferences - Предпочтения + Настройки чатов No comment provided by engineer. @@ -1729,18 +1869,22 @@ set passcode view Chat relay + Чат-релей No comment provided by engineer. Chat relays + Чат-релеи No comment provided by engineer. Chat relays forward messages in channels you create. + Чат-релеи пересылают сообщения в Ваших каналах. No comment provided by engineer. Chat relays forward messages to channel subscribers. + Чат-релеи пересылают сообщения подписчикам каналов. No comment provided by engineer. @@ -1750,18 +1894,19 @@ set passcode view Chat will be deleted for all members - this cannot be undone! - Разговор будет удален для всех участников - это действие нельзя отменить! + Разговор будет удалён для всех участников - это действие нельзя отменить! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! - Разговор будет удален для Вас - это действие нельзя отменить! + Разговор будет удалён для Вас - это действие нельзя отменить! No comment provided by engineer. Chat with admins Чат с админами - chat toolbar + chat feature +chat toolbar Chat with member @@ -1770,7 +1915,7 @@ set passcode view Chat with members before they join. - Общайтесь с членами до того как принять их. + Общайтесь с членами группы до того как принять их. No comment provided by engineer. @@ -1778,11 +1923,26 @@ set passcode view Чаты No comment provided by engineer. + + Chats with admins are prohibited. + Чаты с админами запрещены. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Чаты с админами в публичных каналах не имеют E2E шифрования - используйте только с доверенными чат-релеями. + alert message + Chats with members Чаты с членами группы No comment provided by engineer. + + Chats with members are disabled + Чаты с членами группы отключены + No comment provided by engineer. + Check messages every 20 min. Проверять сообщения каждые 20 минут. @@ -1795,10 +1955,12 @@ set passcode view Check relay address and try again. + Проверьте адрес релея и попробуйте снова. alert message Check relay name and try again. + Проверьте имя релея и попробуйте снова. alert message @@ -1813,7 +1975,7 @@ set passcode view Choose _Migrate from another device_ on the new device and scan QR code. - Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR код. + Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR-код. No comment provided by engineer. @@ -1943,16 +2105,12 @@ set passcode view Configure ICE servers - Настройка ICE серверов + Настройка ICE-серверов No comment provided by engineer. Configure relays - No comment provided by engineer. - - - Configure server operators - Настроить операторов серверов + Настроить релеи No comment provided by engineer. @@ -1997,7 +2155,7 @@ set passcode view Confirm that you remember database passphrase to migrate it. - Подтвердите, что Вы помните пароль базы данных для ее миграции. + Подтвердите, что Вы помните пароль базы данных для её миграции. No comment provided by engineer. @@ -2060,6 +2218,11 @@ This is your own one-time link! Соединиться через ссылку new chat sheet title + + Connect via link or QR code + Соединитесь по ссылке или QR + No comment provided by engineer. + Connect via one-time link Соединиться через одноразовую ссылку @@ -2138,10 +2301,11 @@ This is your own one-time link! Connection error (AUTH) Ошибка соединения (AUTH) - No comment provided by engineer. + conn error description Connection failed + Ошибка соединения No comment provided by engineer. @@ -2196,6 +2360,11 @@ This is your own one-time link! Соединения No comment provided by engineer. + + Contact address + Адрес контакта + chat link info line + Contact allows Контакт разрешает @@ -2208,7 +2377,7 @@ This is your own one-time link! Contact deleted! - Контакт удален! + Контакт удалён! No comment provided by engineer. @@ -2223,7 +2392,7 @@ This is your own one-time link! Contact is deleted. - Контакт удален. + Контакт удалён. No comment provided by engineer. @@ -2243,7 +2412,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! - Контакт будет удален — это нельзя отменить! + Контакт будет удалён - это нельзя отменить! No comment provided by engineer. @@ -2266,19 +2435,24 @@ This is your own one-time link! Продолжить No comment provided by engineer. + + Contribute + Внести свой вклад + No comment provided by engineer. + Conversation deleted! - Разговор удален! + Разговор удалён! No comment provided by engineer. Copy - Скопировать + Копировать No comment provided by engineer. Copy error - Ошибка копирования + Скопировать ошибку No comment provided by engineer. @@ -2296,11 +2470,6 @@ This is your own one-time link! Исправить имя на %@? alert message - - Create - Создать - No comment provided by engineer. - Create 1-time link Создать одноразовую ссылку @@ -2353,10 +2522,12 @@ This is your own one-time link! Create public channel + Создать публичный канал No comment provided by engineer. Create public channel (BETA) + Создать публичный канал (БЕТА) No comment provided by engineer. @@ -2369,11 +2540,21 @@ This is your own one-time link! Создайте Ваш адрес No comment provided by engineer. + + Create your link + Создайте Вашу ссылку + No comment provided by engineer. + Create your profile Создать профиль No comment provided by engineer. + + Create your public address + Создайте Ваш публичный адрес + No comment provided by engineer. + Created Создано @@ -2396,6 +2577,7 @@ This is your own one-time link! Creating channel + Создание канала No comment provided by engineer. @@ -2481,7 +2663,7 @@ This is your own one-time link! Database encryption passphrase will be updated and stored in the keychain. - Пароль базы данных будет изменен и сохранен в Keychain. + Пароль базы данных будет изменен и сохранён в Keychain. No comment provided by engineer. @@ -2519,12 +2701,12 @@ This is your own one-time link! Database passphrase is different from saved in the keychain. - Пароль базы данных отличается от сохраненного в Keychain. + Пароль базы данных отличается от сохранённого в Keychain. No comment provided by engineer. Database passphrase is required to open chat. - Введите пароль базы данных чтобы открыть чат. + Введите пароль базы данных, чтобы открыть чат. No comment provided by engineer. @@ -2535,7 +2717,7 @@ This is your own one-time link! Database will be encrypted and the passphrase stored in the keychain. - База данных будет зашифрована и пароль сохранен в Keychain. + База данных будет зашифрована и пароль сохранён в Keychain. No comment provided by engineer. @@ -2556,13 +2738,9 @@ This is your own one-time link! Отладка доставки No comment provided by engineer. - - Decentralized - Децентрализованный - No comment provided by engineer. - Decode link + Расшифровать ссылку relay test step @@ -2613,10 +2791,12 @@ swipe action Delete channel + Удалить канал No comment provided by engineer. Delete channel? + Удалить канал? No comment provided by engineer. @@ -2691,7 +2871,7 @@ swipe action Delete for everyone - Удалить для всех + Удаление для всех chat feature @@ -2731,15 +2911,17 @@ swipe action Delete member message? - Удалить сообщение участника? + Удалить сообщение члена группы\? No comment provided by engineer. Delete member messages + Удалить сообщения члена группы No comment provided by engineer. Delete member messages? + Удалить сообщения члена группы? alert title @@ -2790,6 +2972,7 @@ alert button Delete relay + Удалить релей No comment provided by engineer. @@ -2949,14 +3132,24 @@ alert button Direct messages between members are prohibited in this chat. - Личные сообщения запрещены в этой группе. + Прямые сообщения между членами группы запрещены. No comment provided by engineer. Direct messages between members are prohibited. - Прямые сообщения между членами запрещены. + Прямые сообщения между членами группы запрещены. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Прямые сообщения между подписчиками запрещены. + No comment provided by engineer. + + + Disable + Выключить + alert button + Disable (keep overrides) Выключить (кроме исключений) @@ -3004,7 +3197,7 @@ alert button Disappearing messages are prohibited. - Исчезающие сообщения запрещены в этой группе. + Исчезающие сообщения запрещены. No comment provided by engineer. @@ -3019,7 +3212,7 @@ alert button Disconnect - Разрыв соединения + Отключить server test step @@ -3062,9 +3255,14 @@ alert button Не отправлять историю новым членам. No comment provided by engineer. + + Do not send history to new subscribers. + Не отправлять историю новым подписчикам. + No comment provided by engineer. + Do not use credentials with proxy. - Не использовать учетные данные с прокси. + Не использовать учётные данные с прокси. No comment provided by engineer. @@ -3110,7 +3308,7 @@ chat item action Download errors - Ошибки приема + Ошибки приёма No comment provided by engineer. @@ -3163,6 +3361,11 @@ chat item action E2E зашифрованные нотификации. No comment provided by engineer. + + Easier to invite your friends 👋 + Проще пригласить друзей 👋 + No comment provided by engineer. + Edit Редактировать @@ -3170,6 +3373,7 @@ chat item action Edit channel profile + Редактировать профиль канала No comment provided by engineer. @@ -3185,7 +3389,7 @@ chat item action Enable Включить - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3209,6 +3413,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Включите хотя бы один чат-релей в настройках Сеть и серверы. channel creation warning @@ -3221,6 +3426,11 @@ chat item action Включить доступ к камере No comment provided by engineer. + + Enable chats with admins? + Включить чаты с админами? + alert title + Enable disappearing messages by default. Включите исчезающие сообщения по умолчанию. @@ -3241,16 +3451,16 @@ chat item action Включить мгновенные уведомления? No comment provided by engineer. + + Enable link previews? + Включить картинки ссылок? + alert title + Enable lock Включить блокировку No comment provided by engineer. - - Enable notifications - Включить уведомления - No comment provided by engineer. - Enable periodic notifications? Включить периодические уведомления? @@ -3293,7 +3503,7 @@ chat item action Encrypt stored files & media - Шифруйте сохраненные файлы и медиа + Шифруйте сохранённые файлы и медиа No comment provided by engineer. @@ -3328,7 +3538,7 @@ chat item action Encrypted message: no passphrase - Зашифрованное сообщение: пароль не сохранен + Зашифрованное сообщение: пароль не сохранён notification @@ -3358,6 +3568,7 @@ chat item action Enter channel name… + Введите имя канала… No comment provided by engineer. @@ -3385,8 +3596,14 @@ chat item action Введите пароль выше, чтобы раскрыть! No comment provided by engineer. + + Enter profile name... + Введите имя профиля... + No comment provided by engineer. + Enter relay name… + Введите имя релея… No comment provided by engineer. @@ -3417,7 +3634,7 @@ chat item action Error Ошибка - No comment provided by engineer. + conn error description Error aborting address change @@ -3426,7 +3643,7 @@ chat item action Error accepting conditions - Ошибка приема условий + Ошибка приёма условий alert title @@ -3446,6 +3663,7 @@ chat item action Error adding relay + Ошибка добавления релея alert title @@ -3510,6 +3728,7 @@ chat item action Error creating channel + Ошибка при создании канала alert title @@ -3689,11 +3908,12 @@ chat item action Error saving ICE servers - Ошибка при сохранении ICE серверов + Ошибка при сохранении ICE-серверов No comment provided by engineer. Error saving channel profile + Ошибка при сохранении профиля канала No comment provided by engineer. @@ -3761,6 +3981,11 @@ chat item action Ошибка настроек отчётов о доставке! No comment provided by engineer. + + Error sharing channel + Ошибка при публикации канала + alert title + Error starting chat Ошибка при запуске чата @@ -3931,7 +4156,7 @@ server test error Faster joining and more reliable messages. - Быстрое вступление и надежная доставка сообщений. + Быстрое вступление и надёжная доставка сообщений. No comment provided by engineer. @@ -3970,7 +4195,7 @@ server test error File not found - most likely file was deleted or cancelled. - Файл не найден - скорее всего, файл был удален или отменен. + Файл не найден - скорее всего, файл был удалён или отменен. file error text @@ -4030,7 +4255,7 @@ server test error Files and media are prohibited. - Файлы и медиа запрещены в этой группе. + Файлы и медиа запрещены. No comment provided by engineer. @@ -4045,6 +4270,7 @@ server test error Filter + Фильтр No comment provided by engineer. @@ -4069,7 +4295,7 @@ server test error Find chats faster - Быстро найти чаты + Быстрый поиск чатов No comment provided by engineer. @@ -4084,7 +4310,7 @@ server test error Fingerprint in server address does not match certificate. - Возможно, хэш сертификата в адресе сервера неверный. + Хэш в адресе сервера не соответствует сертификату. relay test error server test error @@ -4120,7 +4346,7 @@ server test error Fix not supported by group member - Починка не поддерживается членом группы. + Починка не поддерживается членом группы No comment provided by engineer. @@ -4128,6 +4354,11 @@ server test error Для всех модераторов No comment provided by engineer. + + For anyone to reach you + Любой может связаться с Вами + No comment provided by engineer. + For chat profile %@: Для профиля чата %@: @@ -4275,6 +4506,7 @@ Error: %2$@ Get link + Получить ссылку relay test step @@ -4282,6 +4514,11 @@ Error: %2$@ Уведомления, когда Вас упомянули. No comment provided by engineer. + + Get started + Начать + No comment provided by engineer. + Good afternoon! Добрый день! @@ -4340,7 +4577,7 @@ Error: %2$@ Group link Ссылка группы - No comment provided by engineer. + chat link info line Group links @@ -4404,7 +4641,7 @@ Error: %2$@ Help admins moderating their groups. - Помогайте администраторам модерировать их группы. + Помогайте админам модерировать их группы. No comment provided by engineer. @@ -4424,7 +4661,7 @@ Error: %2$@ Hide - Спрятать + Скрыть chat item action @@ -4452,6 +4689,11 @@ Error: %2$@ История не отправляется новым членам. No comment provided by engineer. + + History is not sent to new subscribers. + История не отправляется новым подписчикам. + No comment provided by engineer. + How SimpleX works Как SimpleX работает @@ -4479,7 +4721,7 @@ Error: %2$@ How to use it - Про адрес + Как использовать No comment provided by engineer. @@ -4494,12 +4736,12 @@ Error: %2$@ ICE servers (one per line) - ICE серверы (один на строке) + ICE-серверы (один на строке) No comment provided by engineer. IP address - IP адрес + IP-адрес No comment provided by engineer. @@ -4519,6 +4761,7 @@ Error: %2$@ If you joined or created channels, they will stop working permanently. + Если Вы присоединились к каналам или создали их, они перестанут работать навсегда. down migration warning @@ -4543,6 +4786,7 @@ Error: %2$@ Images + Изображения No comment provided by engineer. @@ -4550,11 +4794,6 @@ Error: %2$@ Сразу No comment provided by engineer. - - Immune to spam - Защищен от спама - No comment provided by engineer. - Import Импортировать @@ -4648,7 +4887,7 @@ More improvements are coming soon! Incognito mode protects your privacy by using a new random profile for each contact. - Режим Инкогнито защищает Вашу конфиденциальность — для каждого контакта создается новый случайный профиль. + Режим Инкогнито защищает Вашу конфиденциальность - для каждого контакта создаётся новый случайный профиль. No comment provided by engineer. @@ -4696,9 +4935,9 @@ More improvements are coming soon! Роль при вступлении No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - [SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + SimpleX Chat для терминала No comment provided by engineer. @@ -4750,13 +4989,13 @@ More improvements are coming soon! Invalid QR code - Неверный QR код + Ошибка QR-кода No comment provided by engineer. Invalid connection link Ошибка в ссылке контакта - No comment provided by engineer. + conn error description Invalid display name! @@ -4780,10 +5019,12 @@ More improvements are coming soon! Invalid relay address! + Неверный адрес релея! alert title Invalid relay name! + Неверное имя релея! alert title @@ -4813,11 +5054,17 @@ More improvements are coming soon! Invite member + Пригласить члена группы No comment provided by engineer. Invite members - Пригласить членов группы + Пригласить в группу + No comment provided by engineer. + + + Invite someone privately + Пригласите конфиденциально No comment provided by engineer. @@ -4837,12 +5084,12 @@ More improvements are coming soon! Irreversible message deletion is prohibited in this chat. - Необратимое удаление сообщений запрещено в этом чате. + Необратимое удаление сообщений запрещено. No comment provided by engineer. Irreversible message deletion is prohibited. - Необратимое удаление сообщений запрещено в этой группе. + Необратимое удаление сообщений запрещено. No comment provided by engineer. @@ -4868,7 +5115,7 @@ More improvements are coming soon! It protects your IP address and connections. - Защищает ваш IP адрес и соединения. + Защищает ваш IP-адрес и соединения. No comment provided by engineer. @@ -4893,11 +5140,12 @@ More improvements are coming soon! Join as %@ - вступить как %@ + Вступить как %s No comment provided by engineer. Join channel + Вступить в канал No comment provided by engineer. @@ -4989,10 +5237,12 @@ This is your link for group %@! Leave channel + Покинуть канал No comment provided by engineer. Leave channel? + Выйти из канала? No comment provided by engineer. @@ -5020,6 +5270,11 @@ This is your link for group %@! Меньше трафик в мобильных сетях. No comment provided by engineer. + + Let someone connect to you + Дайте собеседнику Вашу ссылку + No comment provided by engineer. + Let's talk in SimpleX Chat Давайте поговорим в SimpleX Chat @@ -5040,6 +5295,11 @@ This is your link for group %@! Свяжите мобильное и настольное приложения! 🔗 No comment provided by engineer. + + Link signature verified. + Подпись ссылки проверена. + owner verification + Linked desktop options Опции связанных компьютеров @@ -5052,6 +5312,7 @@ This is your link for group %@! Links + Ссылки No comment provided by engineer. @@ -5116,12 +5377,12 @@ This is your link for group %@! Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. - Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. + Пожалуйста, проверьте, что адреса WebRTC ICE-серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. No comment provided by engineer. Mark deleted for everyone - Пометить как удаленное для всех + Пометить как удалённое для всех No comment provided by engineer. @@ -5171,7 +5432,7 @@ This is your link for group %@! Member inactive - Член неактивен + Член группы неактивен item status text @@ -5181,6 +5442,7 @@ This is your link for group %@! Member messages will be deleted - this cannot be undone! + Сообщения члена группы будут удалены - это нельзя отменить! alert message @@ -5205,17 +5467,17 @@ This is your link for group %@! Member will be removed from chat - this cannot be undone! - Член будет удален из разговора - это действие нельзя отменить! + Член будет удалён из разговора - это действие нельзя отменить! alert message Member will be removed from group - this cannot be undone! - Член группы будет удален - это действие нельзя отменить! + Член группы будет удалён - это действие нельзя отменить! alert message Member will join the group, accept member? - Участник хочет присоединиться к группе. Принять? + Член группы хочет присоединиться. Принять? alert message @@ -5223,6 +5485,11 @@ This is your link for group %@! Члены могут добавлять реакции на сообщения. No comment provided by engineer. + + Members can chat with admins. + Члены группы могут общаться с админами. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Члены могут необратимо удалять отправленные сообщения. (24 часа) @@ -5235,12 +5502,12 @@ This is your link for group %@! Members can send SimpleX links. - Члены могут отправлять ссылки SimpleX. + Члены группы могут отправлять ссылки SimpleX. No comment provided by engineer. Members can send direct messages. - Члены могут посылать прямые сообщения. + Члены могут посылать личные сообщения. No comment provided by engineer. @@ -5255,12 +5522,12 @@ This is your link for group %@! Members can send voice messages. - Члены могут отправлять голосовые сообщения. + Члены группы могут отправлять голосовые сообщения. No comment provided by engineer. Mention members 👋 - Упоминайте участников 👋 + Упоминайте членов группы 👋 No comment provided by engineer. @@ -5275,7 +5542,7 @@ This is your link for group %@! Message delivery receipts! - Отчеты о доставке сообщений! + Отчёты о доставке сообщений! No comment provided by engineer. @@ -5290,6 +5557,7 @@ This is your link for group %@! Message error + Ошибка сообщения No comment provided by engineer. @@ -5324,12 +5592,12 @@ This is your link for group %@! Message reactions are prohibited. - Реакции на сообщения запрещены в этой группе. + Реакции на сообщения запрещены. No comment provided by engineer. Message reception - Прием сообщений + Приём сообщений No comment provided by engineer. @@ -5379,7 +5647,7 @@ This is your link for group %@! Messages are protected by **end-to-end encryption**. - Сообщения защищены **end-to-end шифрованием**. + Сообщения защищены **сквозным шифрованием**. No comment provided by engineer. @@ -5387,6 +5655,16 @@ This is your link for group %@! Сообщения от %@ будут показаны! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + Сообщения в этом канале **не защищены сквозным шифрованием**. Чат-релеи могут видеть эти сообщения. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + Сообщения в этом канале не защищены сквозным шифрованием. Чат-релеи могут видеть эти сообщения. + E2EE info chat item + Messages in this chat will never be deleted. Сообщения в этом чате никогда не будут удалены. @@ -5409,12 +5687,17 @@ This is your link for group %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. + Сообщения, файлы и звонки защищены **сквозным шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. - Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. + Сообщения, файлы и звонки защищены **квантово-устойчивым сквозным шифрованием** с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. + No comment provided by engineer. + + + Migrate + Мигрировать No comment provided by engineer. @@ -5422,11 +5705,6 @@ This is your link for group %@! Мигрировать устройство No comment provided by engineer. - - Migrate from another device - Миграция с другого устройства - No comment provided by engineer. - Migrate here Мигрировать сюда @@ -5439,7 +5717,7 @@ This is your link for group %@! Migrate to another device via QR code. - Мигрируйте на другое устройство через QR код. + Мигрируйте на другое устройство через QR-код. No comment provided by engineer. @@ -5504,12 +5782,12 @@ This is your link for group %@! More reliable network connection. - Более надежное соединение с сетью. + Более надёжное соединение с сетью. No comment provided by engineer. More reliable notifications - Более надежные уведомления + Более надёжные уведомления No comment provided by engineer. @@ -5547,6 +5825,11 @@ This is your link for group %@! Сеть и серверы No comment provided by engineer. + + Network commitments + Обязательства сети + No comment provided by engineer. + Network connection Интернет-соединение @@ -5557,6 +5840,11 @@ This is your link for group %@! Децентрализация сети No comment provided by engineer. + + Network error + Ошибка сети + conn error description + Network issues - message expired after many attempts to send it. Ошибка сети - сообщение не было отправлено после многократных попыток. @@ -5572,6 +5860,13 @@ This is your link for group %@! Оператор сети No comment provided by engineer. + + Network routers cannot know +who talks to whom + Серверы сети не могут знать, +кто с кем общается + No comment provided by engineer. + Network settings Настройки сети @@ -5587,6 +5882,11 @@ This is your link for group %@! Новый token status text + + New 1-time link + Новая одноразовая ссылка + No comment provided by engineer. + New Passcode Новый Код @@ -5594,12 +5894,12 @@ This is your link for group %@! New SOCKS credentials will be used every time you start the app. - Новые учетные данные SOCKS будут использоваться при каждом запуске приложения. + Новые учётные данные SOCKS будут использоваться при каждом запуске приложения. No comment provided by engineer. New SOCKS credentials will be used for each server. - Новые учетные данные SOCKS будут использоваться для каждого сервера. + Новые учётные данные SOCKS будут использоваться для каждого сервера. No comment provided by engineer. @@ -5614,6 +5914,7 @@ This is your link for group %@! New chat relay + Новый чат-релей No comment provided by engineer. @@ -5663,7 +5964,7 @@ This is your link for group %@! New member wants to join the group. - Новый участник хочет присоединиться к группе. + Новый член группы хочет присоединиться. rcv group event chat item @@ -5686,6 +5987,18 @@ This is your link for group %@! Нет No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + Без аккаунта. Без номера. Без email. Без ID. +Самое безопасное шифрование. + No comment provided by engineer. + + + No active relays + Нет активных релеев + No comment provided by engineer. + No app password Нет кода доступа @@ -5693,10 +6006,12 @@ This is your link for group %@! No chat relays + Нет чат-релеев No comment provided by engineer. No chat relays enabled. + Чат-релеи не включены. servers warning @@ -5821,12 +6136,12 @@ This is your link for group %@! No servers to receive files. - Нет серверов для приема файлов. + Нет серверов для приёма файлов. servers error No servers to receive messages. - Нет серверов для приема сообщений. + Нет серверов для приёма сообщений. servers error @@ -5844,13 +6159,24 @@ This is your link for group %@! Нет непрочитанных чатов No comment provided by engineer. - - No user identifiers. - Без идентификаторов пользователей. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Никто не отслеживал ваши разговоры. Никто не составлял карту ваших перемещений. Конфиденциальность не была функцией - это был образ жизни. + No comment provided by engineer. + + + Non-profit governance + Некоммерческое управление + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Не более надёжный замок на чужой двери. Не более вежливый хозяин, который уважает вашу частную жизнь, но всё равно ведёт учёт всех посетителей. Вы не гость. Вы у себя дома. Ни один король не войдёт в ваш дом - вы суверенны. No comment provided by engineer. Not all relays connected + Не все релеи подключены alert title @@ -5910,11 +6236,11 @@ This is your link for group %@! OK OK - No comment provided by engineer. + alert button Off - Выключено + Нет blur media @@ -5929,11 +6255,21 @@ new chat action Предыдущая версия данных чата No comment provided by engineer. + + On your phone, not on servers. + На Вашем телефоне, не на серверах. + No comment provided by engineer. + One-time invitation link Одноразовая ссылка No comment provided by engineer. + + One-time link + Одноразовая ссылка + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5953,6 +6289,11 @@ Requires compatible VPN. Onion хосты не используются. No comment provided by engineer. + + Only channel owners can change channel preferences. + Изменить настройки канала могут только владельцы канала. + No comment provided by engineer. + Only chat owners can change preferences. Только владельцы разговора могут поменять предпочтения. @@ -6056,7 +6397,8 @@ Requires compatible VPN. Open Открыть - alert action + alert action +alert button Open Settings @@ -6070,6 +6412,7 @@ Requires compatible VPN. Open channel + Открыть канал new chat action @@ -6092,6 +6435,11 @@ Requires compatible VPN. Открыть условия No comment provided by engineer. + + Open external link? + Открыть внешнюю ссылку? + alert title + Open full link Открыть полную ссылку @@ -6114,6 +6462,7 @@ Requires compatible VPN. Open new channel + Открыть новый канал new chat action @@ -6161,6 +6510,17 @@ Requires compatible VPN. Сервер оператора alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Операторы обязуются: +- Быть независимыми +- Минимизировать использование метаданных +- Использовать проверенный и открытый исходный код + No comment provided by engineer. + Or import archive file Или импортировать файл архива @@ -6173,7 +6533,7 @@ Requires compatible VPN. Or scan QR code - Или отсканируйте QR код + Или отсканируйте QR-код No comment provided by engineer. @@ -6181,6 +6541,11 @@ Requires compatible VPN. Или передайте эту ссылку No comment provided by engineer. + + Or show QR in person or via video call. + Или покажите QR лично или через видеозвонок. + No comment provided by engineer. + Or show this code Или покажите этот код @@ -6191,6 +6556,11 @@ Requires compatible VPN. Или поделиться конфиденциально No comment provided by engineer. + + Or use this QR - print or show online. + Или используйте этот QR - распечатайте или покажите онлайн. + No comment provided by engineer. + Organize chats into lists Организуйте чаты в списки @@ -6210,10 +6580,17 @@ Requires compatible VPN. Owner + Владелец No comment provided by engineer. Owners + Владельцы + No comment provided by engineer. + + + Ownership: you can run your own relays. + Владение: Вы можете запустить свои собственные релеи. No comment provided by engineer. @@ -6271,6 +6648,11 @@ Requires compatible VPN. Вставить изображение No comment provided by engineer. + + Paste link / Scan + Вставить ссылку / Сканировать + No comment provided by engineer. + Paste link to connect! Вставьте ссылку, чтобы соединиться! @@ -6325,12 +6707,12 @@ Please share any other issues with the developers. Please check that you used the correct link or ask your contact to send you another one. - Пожалуйста, проверьте, что Вы использовали правильную ссылку или попросите, чтобы Ваш контакт отправил Вам другую ссылку. + Пожалуйста, проверьте, что Вы использовали правильную ссылку, или попросите Ваш контакт отправить Вам новую. No comment provided by engineer. Please check your network connection with %@ and try again. - Пожалуйста, проверьте Ваше соединение с %@ и попробуйте еще раз. + Пожалуйста, проверьте Ваше соединение с %@ и попробуйте ещё раз. alert message @@ -6372,7 +6754,7 @@ Error: %@ Please report it to the developers. - Пожалуйста, сообщите об этой ошибке девелоперам. + Пожалуйста, сообщите об этой ошибке разработчикам. No comment provided by engineer. @@ -6382,12 +6764,12 @@ Error: %@ Please store passphrase securely, you will NOT be able to access chat if you lose it. - Пожалуйста, надежно сохраните пароль, Вы НЕ сможете открыть чат, если потеряете его. + Пожалуйста, надёжно сохраните пароль, Вы НЕ сможете открыть чат, если потеряете его. No comment provided by engineer. Please store passphrase securely, you will NOT be able to change it if you lose it. - Пожалуйста, надежно сохраните пароль, Вы НЕ сможете его поменять, если потеряете. + Пожалуйста, надёжно сохраните пароль, Вы НЕ сможете его поменять, если потеряете. No comment provided by engineer. @@ -6427,10 +6809,12 @@ Error: %@ Preset relay address + Адрес релея по умолчанию No comment provided by engineer. Preset relay name + Имя релея по умолчанию No comment provided by engineer. @@ -6468,19 +6852,19 @@ Error: %@ Политика конфиденциальности и условия использования. No comment provided by engineer. - - Privacy redefined - Более конфиденциальный + + Privacy: for owners and subscribers. + Конфиденциальность: для владельцев и подписчиков. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. + + Private and secure messaging. + Конфиденциальный и безопасный обмен сообщениями. No comment provided by engineer. Private filenames - Защищенные имена файлов + Защищённые имена файлов No comment provided by engineer. @@ -6520,6 +6904,7 @@ Error: %@ Proceed + Продолжить alert action @@ -6547,9 +6932,9 @@ Error: %@ Тема профиля No comment provided by engineer. - - Profile update will be sent to your contacts. - Обновлённый профиль будет отправлен Вашим контактам. + + Profile update will be sent to your SimpleX contacts. + Обновление профиля будет отправлено Вашим SimpleX контактам. alert message @@ -6557,6 +6942,11 @@ Error: %@ Запретить аудио/видео звонки. No comment provided by engineer. + + Prohibit chats with admins. + Запретить чаты с админами. + No comment provided by engineer. + Prohibit irreversible message deletion. Запретить необратимое удаление сообщений. @@ -6584,27 +6974,32 @@ Error: %@ Prohibit sending direct messages to members. - Запретить посылать прямые сообщения членам группы. + Запретить посылать личные сообщения членам группы. + No comment provided by engineer. + + + Prohibit sending direct messages to subscribers. + Запретить отправку личных сообщений подписчикам. No comment provided by engineer. Prohibit sending disappearing messages. - Запретить посылать исчезающие сообщения. + Запретить отправлять исчезающие сообщения. No comment provided by engineer. Prohibit sending files and media. - Запретить слать файлы и медиа. + Запретить отправлять файлы и медиа. No comment provided by engineer. Prohibit sending voice messages. - Запретить отправлять голосовые сообщений. + Запретить отправлять голосовые сообщения. No comment provided by engineer. Protect IP address - Защитить IP адрес + Защитить IP-адрес No comment provided by engineer. @@ -6615,7 +7010,7 @@ Error: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. - Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. + Защитите ваш IP-адрес от серверов сообщений, выбранных Вашими контактами. Включите в настройках *Сети и серверов*. No comment provided by engineer. @@ -6654,6 +7049,11 @@ Enable in *Network & servers* settings. Прокси требует пароль No comment provided by engineer. + + Public channels - speak freely 🚀 + Публичные каналы - говорите свободно 🚀 + No comment provided by engineer. + Push notifications Доставка уведомлений @@ -6694,24 +7094,14 @@ Enable in *Network & servers* settings. Узнать больше 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. + Узнать больше в Руководстве пользователя. No comment provided by engineer. - - 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). - 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). + + Read more in our GitHub repository. + Узнайте больше из нашего GitHub репозитория. No comment provided by engineer. @@ -6721,7 +7111,7 @@ Enable in *Network & servers* settings. Receive errors - Ошибки приема + Ошибки приёма No comment provided by engineer. @@ -6868,37 +7258,52 @@ swipe action Reject member? - Отклонить участника? + Отклонить члена группы? alert title Relay + Релей No comment provided by engineer. Relay address + Адрес релея alert title Relay connection failed + Ошибка подключения релея alert title Relay link + Ссылка релея No comment provided by engineer. + + Relay results: + Результаты релея: + alert message + Relay server is only used if necessary. Another party can observe your IP address. - Relay сервер используется только при необходимости. Другая сторона может видеть Ваш IP адрес. + Релей-сервер используется только при необходимости. Другая сторона может видеть Ваш IP-адрес. No comment provided by engineer. Relay server protects your IP address, but it can observe the duration of the call. - Relay сервер защищает Ваш IP адрес, но может отслеживать продолжительность звонка. + Релей-сервер защищает Ваш IP-адрес, но может отслеживать продолжительность звонка. No comment provided by engineer. Relay test failed! + Тест релея не пройден! + No comment provided by engineer. + + + Reliability: many relays per channel. + Надёжность: несколько релеев на каждый канал. No comment provided by engineer. @@ -6908,6 +7313,7 @@ swipe action Remove and delete messages + Удалить вместе с сообщениями alert action @@ -6942,10 +7348,12 @@ swipe action Remove subscriber + Удалить подписчика No comment provided by engineer. Remove subscriber? + Удалить подписчика? alert title @@ -7090,12 +7498,12 @@ swipe action Restart the app to create a new chat profile - Перезапустите приложение, чтобы создать новый профиль. + Перезапустите приложение, чтобы создать новый профиль No comment provided by engineer. Restart the app to use imported chat database - Перезапустите приложение, чтобы использовать импортированные данные чата. + Перезапустите приложение, чтобы использовать импортированные данные чата No comment provided by engineer. @@ -7140,7 +7548,7 @@ swipe action Review members - Одобрять членов + Одобрять членов группы admission stage @@ -7175,12 +7583,17 @@ swipe action SMP server - SMP сервер + SMP-сервер No comment provided by engineer. SOCKS proxy - SOCKS прокси + SOCKS-прокси + No comment provided by engineer. + + + Safe web links + Безопасные веб-ссылки No comment provided by engineer. @@ -7211,6 +7624,7 @@ chat item action Save (and notify subscribers) + Сохранить (и уведомить подписчиков) alert button @@ -7228,6 +7642,11 @@ chat item action Сохранить и уведомить членов группы No comment provided by engineer. + + Save and notify subscribers + Сохранить и уведомить подписчиков + No comment provided by engineer. + Save and reconnect Сохранить и переподключиться @@ -7240,10 +7659,12 @@ chat item action Save channel profile + Сохранить профиль канала No comment provided by engineer. Save channel profile? + Сохранить профиль канала? alert title @@ -7308,7 +7729,7 @@ chat item action Saved WebRTC ICE servers will be removed - Сохраненные WebRTC ICE серверы будут удалены + Сохранённые WebRTC ICE-серверы будут удалены No comment provided by engineer. @@ -7318,7 +7739,7 @@ chat item action Saved message - Сохраненное сообщение + Сохранённое сообщение message info title @@ -7338,12 +7759,12 @@ chat item action Scan QR code - Сканировать QR код + Сканировать QR-код No comment provided by engineer. Scan QR code from desktop - Сканировать QR код с компьютера + Сканировать QR-код с компьютера No comment provided by engineer. @@ -7358,7 +7779,7 @@ chat item action Scan server QR code - Сканировать QR код сервера + Сканировать QR-код сервера No comment provided by engineer. @@ -7373,27 +7794,32 @@ chat item action Search files + Поиск файлов No comment provided by engineer. Search images + Поиск изображений No comment provided by engineer. Search links + Поиск ссылок No comment provided by engineer. Search or paste SimpleX link - Искать или вставьте ссылку SimpleX + Искать или вставить ссылку SimpleX No comment provided by engineer. Search videos + Поиск видео No comment provided by engineer. Search voice messages + Поиск голосовых сообщений No comment provided by engineer. @@ -7421,6 +7847,11 @@ chat item action Код безопасности No comment provided by engineer. + + Security: owners hold channel keys. + Безопасность: владельцы хранят ключи канала. + No comment provided by engineer. + Select Выбрать @@ -7468,7 +7899,7 @@ chat item action Send a live message - it will update for the recipient(s) as you type it - Отправить живое сообщение — оно будет обновляться для получателей по мере того, как Вы его вводите + Отправить живое сообщение - оно будет обновляться для получателей по мере того, как Вы его вводите No comment provided by engineer. @@ -7483,7 +7914,7 @@ chat item action Send direct message to connect - Отправьте сообщение чтобы соединиться + Отправить личное сообщение контакту No comment provided by engineer. @@ -7513,7 +7944,7 @@ chat item action Send messages directly when IP address is protected and your or destination server does not support private routing. - Отправлять сообщения напрямую, когда IP адрес защищен, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + Отправлять сообщения напрямую, когда IP-адрес защищён, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. No comment provided by engineer. @@ -7533,7 +7964,7 @@ chat item action Send questions and ideas - Отправьте вопросы и идеи + Вопросы и предложения No comment provided by engineer. @@ -7551,6 +7982,11 @@ chat item action Отправить запрос без сообщения No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Отправьте ссылку через любой мессенджер - это безопасно. Попросите вставить её в SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Отправьте из галереи или из дополнительных клавиатур. @@ -7561,6 +7997,11 @@ chat item action Отправить до 100 последних сообщений новым членам. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Отправлять до 100 последних сообщений новым подписчикам. + No comment provided by engineer. + Send your private feedback to groups. Отправляйте Ваши конфиденциальные предложения группе. @@ -7576,6 +8017,11 @@ chat item action Отправитель мог удалить запрос на соединение. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Отправка картинки ссылки может раскрыть Ваш IP-адрес веб-сайту. Вы можете изменить это в настройках безопасности позже. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Отправка отчётов о доставке будет включена для всех контактов во всех видимых профилях чата. @@ -7688,7 +8134,7 @@ chat item action Server operator changed. - Оператор серверов изменен. + Оператор сервера изменен. alert title @@ -7703,6 +8149,7 @@ chat item action Server requires authorization to connect to relay, check password. + Для подключения к релею требуется авторизация, проверьте пароль. relay test error @@ -7835,6 +8282,16 @@ chat item action Настройки были изменены. alert message + + Setup notifications + Настроить уведомления + No comment provided by engineer. + + + Setup routers + Настроить серверы + No comment provided by engineer. + Shape profile images Форма картинок профилей @@ -7871,11 +8328,16 @@ chat item action Поделитесь адресом No comment provided by engineer. - - Share address with contacts? - Поделиться адресом с контактами? + + Share address with SimpleX contacts? + Поделиться адресом с контактами SimpleX? alert title + + Share channel + Поделиться каналом + No comment provided by engineer. + Share from other apps. Поделитесь из других приложений. @@ -7903,6 +8365,7 @@ chat item action Share relay address + Поделиться адресом релея No comment provided by engineer. @@ -7915,9 +8378,14 @@ chat item action Поделиться в SimpleX No comment provided by engineer. - - Share with contacts - Поделиться с контактами + + Share via chat + Поделиться в чате + No comment provided by engineer. + + + Share with SimpleX contacts + Поделиться с контактами SimpleX No comment provided by engineer. @@ -7942,7 +8410,7 @@ chat item action Show QR code - Показать QR код + Показать QR-код No comment provided by engineer. @@ -7952,7 +8420,7 @@ chat item action Show developer options - Показать опции для девелоперов + Показать опции для разработчиков No comment provided by engineer. @@ -8042,7 +8510,7 @@ chat item action SimpleX address settings - Настройки автоприема + Настройки автоприёма alert title @@ -8067,7 +8535,7 @@ chat item action SimpleX links - SimpleX ссылки + Ссылки SimpleX chat feature @@ -8092,11 +8560,12 @@ chat item action SimpleX relay address + Адрес релея SimpleX simplex link type Simplified incognito mode - Упрощенный режим Инкогнито + Упрощённый режим Инкогнито No comment provided by engineer. @@ -8167,6 +8636,11 @@ report reason Квадрат, круг и все, что между ними. No comment provided by engineer. + + Star on GitHub + Поставить звёздочку на GitHub + No comment provided by engineer. + Start chat Запустить чат @@ -8269,19 +8743,74 @@ report reason Subscriber + Подписчик No comment provided by engineer. + + Subscriber reports + Сообщения о нарушениях + chat feature + Subscriber will be removed from channel - this cannot be undone! + Подписчик будет удалён из канала - это нельзя отменить! alert message Subscribers + Подписчики + No comment provided by engineer. + + + Subscribers can add message reactions. + Подписчики могут добавлять реакции на сообщения. + No comment provided by engineer. + + + Subscribers can chat with admins. + Подписчики могут общаться с админами. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Подписчики могут необратимо удалять отправленные сообщения. (24 часа) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Подписчики могут отправлять сообщения о нарушениях модераторам. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Подписчики могут отправлять ссылки SimpleX. + No comment provided by engineer. + + + Subscribers can send direct messages. + Подписчики могут отправлять личные сообщения. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Подписчики могут отправлять исчезающие сообщения. + No comment provided by engineer. + + + Subscribers can send files and media. + Подписчики могут отправлять файлы и медиа. + No comment provided by engineer. + + + Subscribers can send voice messages. + Подписчики могут отправлять голосовые сообщения. No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Подписчики используют ссылку релея для подключения к каналу. +Адрес релея был использован для настройки этого релея для канала. No comment provided by engineer. @@ -8331,7 +8860,7 @@ Relay address was used to set up this relay for the channel. TCP connection timeout - Таймаут TCP соединения + Таймаут TCP-соединения No comment provided by engineer. @@ -8364,6 +8893,11 @@ Relay address was used to set up this relay for the channel. Сделать фото No comment provided by engineer. + + Talk to someone + Начните разговор + No comment provided by engineer. + Tap Connect to chat Нажмите Соединиться @@ -8379,13 +8913,9 @@ Relay address was used to set up this relay for the channel. Нажмите Соединиться, чтобы использовать бот No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. - No comment provided by engineer. - Tap Join channel + Нажмите Войти в канал No comment provided by engineer. @@ -8400,12 +8930,12 @@ Relay address was used to set up this relay for the channel. Tap to Connect - Нажмите чтобы соединиться + Нажмите, чтобы соединиться No comment provided by engineer. Tap to activate profile. - Нажмите, чтобы сделать профиль активным. + Нажмите на профиль, чтобы переключиться. No comment provided by engineer. @@ -8418,6 +8948,11 @@ Relay address was used to set up this relay for the channel. Нажмите, чтобы вступить инкогнито No comment provided by engineer. + + Tap to open + Нажмите, чтобы открыть + No comment provided by engineer. + Tap to paste link Нажмите, чтобы вставить ссылку @@ -8446,6 +8981,7 @@ server test failure Test relay + Тест релея No comment provided by engineer. @@ -8502,6 +9038,7 @@ It can happen because of some bug or when the connection is compromised. The app removed this message after %lld attempts to receive it. + Приложение удалило это сообщение после %lld попыток его получить. No comment provided by engineer. @@ -8516,9 +9053,14 @@ It can happen because of some bug or when the connection is compromised. The code you scanned is not a SimpleX link QR code. - Этот QR код не является SimpleX-ccылкой. + Этот QR-код не является SimpleX-ccылкой. No comment provided by engineer. + + The connection reached the limit of undelivered messages + Соединение достигло лимита недоставленных сообщений + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. @@ -8544,9 +9086,11 @@ It can happen because of some bug or when the connection is compromised.Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения! No comment provided by engineer. - - The future of messaging - Будущее коммуникаций + + The first network where you own +your contacts and groups. + Первая сеть, в которой Вы владеете +своими контактами и группами. No comment provided by engineer. @@ -8566,7 +9110,7 @@ It can happen because of some bug or when the connection is compromised. The message will be marked as moderated for all members. - Сообщение будет помечено как удаленное для всех членов группы. + Сообщение будет помечено как удалённое для всех членов группы. No comment provided by engineer. @@ -8576,7 +9120,7 @@ It can happen because of some bug or when the connection is compromised. The messages will be marked as moderated for all members. - Сообщения будут помечены как удаленные для всех членов группы. + Сообщения будут помечены как удалённые для всех членов группы. No comment provided by engineer. @@ -8584,9 +9128,14 @@ It can happen because of some bug or when the connection is compromised.Предыдущая версия данных чата не удалена при перемещении, её можно удалить. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Древнейшая человеческая свобода - говорить с другим человеком без слежки - построенная на инфраструктуре, которая не может её предать. + No comment provided by engineer. + The same conditions will apply to operator **%@**. - Те же самые условия будут приняты для оператора **%@**. + Те же условия будут действовать для оператора **%s**. No comment provided by engineer. @@ -8621,7 +9170,7 @@ It can happen because of some bug or when the connection is compromised. The uploaded database archive will be permanently removed from the servers. - Загруженный архив базы данных будет навсегда удален с серверов. + Загруженный архив базы данных будет навсегда удалён с серверов. No comment provided by engineer. @@ -8629,6 +9178,16 @@ It can happen because of some bug or when the connection is compromised.Темы No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Потом мы вышли в интернет, и каждая платформа попросила частичку вас - ваше имя, ваш номер, ваших друзей. Мы смирились с тем, что за возможность общаться приходится отдавать информацию о том, с кем мы общаемся. Каждое поколение людей и технологий жило так - телефон, электронная почта, мессенджеры, социальные сети. Казалось, что другого пути нет. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Другой путь есть. Сеть без номеров телефонов. Без имён пользователей. Без аккаунтов. Без каких-либо идентификаторов пользователей. Сеть, которая соединяет людей и передаёт зашифрованные сообщения, не зная, кто с кем связан. + No comment provided by engineer. + These conditions will also apply for: **%@**. Эти условия также будут применены к: **%@**. @@ -8641,17 +9200,17 @@ It can happen because of some bug or when the connection is compromised. They can be overridden in contact and group settings. - Они могут быть переопределены в настройках контактов и групп. + Они могут быть изменены в настройках контактов и групп. No comment provided by engineer. This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении. + Это действие нельзя отменить - все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении. No comment provided by engineer. This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. - Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут. + Это действие нельзя отменить - все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут. No comment provided by engineer. @@ -8661,17 +9220,17 @@ It can happen because of some bug or when the connection is compromised. This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. + Это действие нельзя отменить - Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. No comment provided by engineer. This chat is protected by end-to-end encryption. - Чат защищен end-to-end шифрованием. + Чат защищён сквозным шифрованием. E2EE info chat item This chat is protected by quantum resistant end-to-end encryption. - Чат защищен квантово-устойчивым end-to-end шифрованием. + Чат защищён квантово-устойчивым сквозным шифрованием. E2EE info chat item @@ -8696,10 +9255,12 @@ It can happen because of some bug or when the connection is compromised. This is a chat relay address, it cannot be used to connect. + Это адрес чат-релея, с ним нельзя соединиться. alert message This is your link for channel %@! + Это ваша ссылка на канал %@! new chat action @@ -8714,7 +9275,7 @@ It can happen because of some bug or when the connection is compromised. This message was deleted or not received yet. - Это сообщение было удалено или еще не получено. + Это сообщение было удалено или ещё не получено. No comment provided by engineer. @@ -8752,6 +9313,11 @@ It can happen because of some bug or when the connection is compromised.Чтобы скрыть нежелательные сообщения. No comment provided by engineer. + + To make SimpleX Network last. + Чтобы сохранить сеть SimpleX для всех. + No comment provided by engineer. + To make a new connection Чтобы соединиться @@ -8769,7 +9335,7 @@ It can happen because of some bug or when the connection is compromised. To protect your IP address, private routing uses your SMP servers to deliver messages. - Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений. + Чтобы защитить Ваш IP-адрес, приложение использует Ваши SMP-серверы для конфиденциальной доставки сообщений. No comment provided by engineer. @@ -8781,7 +9347,7 @@ You will be prompted to complete authentication before this feature is enabled.< To protect your privacy, SimpleX uses separate IDs for each of your contacts. - Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта. + Чтобы защитить Вашу конфиденциальность, SimpleX использует разные ID для каждого Вашего контакта. No comment provided by engineer. @@ -8836,12 +9402,7 @@ You will be prompted to complete authentication before this feature is enabled.< To verify end-to-end encryption with your contact compare (or scan) the code on your devices. - Чтобы подтвердить end-to-end шифрование с Вашим контактом сравните (или сканируйте) код безопасности на Ваших устройствах. - No comment provided by engineer. - - - Toggle chat list: - Переключите список чатов: + Чтобы подтвердить безопасность сквозного шифрования с Вашим контактом сравните (или сканируйте) код на ваших устройствах. No comment provided by engineer. @@ -8859,6 +9420,11 @@ You will be prompted to complete authentication before this feature is enabled.< Прозрачность тулбара No comment provided by engineer. + + Top bar + Верхнее меню + No comment provided by engineer. + Total Всего @@ -8866,7 +9432,7 @@ You will be prompted to complete authentication before this feature is enabled.< Transport isolation - Отдельные сессии для + Отдельные транспортные сессии No comment provided by engineer. @@ -8876,7 +9442,7 @@ You will be prompted to complete authentication before this feature is enabled.< Trying to connect to the server used to receive messages from this connection. - Попытка подключиться к серверу, используемому для получения сообщений от этого соединения. + Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта. subscription status explanation @@ -8926,6 +9492,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock subscriber for all? + Разблокировать подписчика для всех? No comment provided by engineer. @@ -8992,7 +9559,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. To connect, please ask your contact to create another connection link and check that you have a stable network connection. Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом. -Чтобы установить соединение, попросите Ваш контакт создать еще одну ссылку и проверьте Ваше соединение с сетью. +Чтобы установить соединение, попросите Ваш контакт создать ещё одну ссылку и проверьте Ваше соединение с сетью. No comment provided by engineer. @@ -9028,13 +9595,18 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link Ссылка не поддерживается - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. До 100 последних сообщений отправляются новым членам. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + До 100 последних сообщений отправляется новым подписчикам. + No comment provided by engineer. + Update Обновить @@ -9057,12 +9629,12 @@ To connect, please ask your contact to create another connection link and check Updated conditions - Обновленные условия + Обновлённые условия No comment provided by engineer. Updating settings will re-connect the client to all servers. - Обновление настроек приведет к сбросу и установке нового соединения со всеми серверами. + Обновление настроек приведёт к сбросу и установке нового соединения со всеми серверами. No comment provided by engineer. @@ -9142,7 +9714,7 @@ To connect, please ask your contact to create another connection link and check Use SOCKS proxy - Использовать SOCKS прокси + Использовать SOCKS-прокси No comment provided by engineer. @@ -9160,11 +9732,6 @@ To connect, please ask your contact to create another connection link and check Использовать TCP-порт 443 только для серверов по умолчанию. No comment provided by engineer. - - Use chat - Использовать чат - No comment provided by engineer. - Use current profile Использовать активный профиль @@ -9182,6 +9749,7 @@ To connect, please ask your contact to create another connection link and check Use for new channels + Использовать для новых каналов No comment provided by engineer. @@ -9206,7 +9774,7 @@ To connect, please ask your contact to create another connection link and check Use new incognito profile - Использовать новый Инкогнито профиль + Использовать новый профиль инкогнито new chat action @@ -9216,7 +9784,7 @@ To connect, please ask your contact to create another connection link and check Use private routing with unknown servers when IP address is not protected. - Использовать конфиденциальную доставку с неизвестными серверами, когда IP адрес не защищен. + Использовать конфиденциальную доставку с неизвестными серверами, когда IP-адрес не защищён. No comment provided by engineer. @@ -9226,6 +9794,7 @@ To connect, please ask your contact to create another connection link and check Use relay + Использовать релей No comment provided by engineer. @@ -9248,6 +9817,11 @@ To connect, please ask your contact to create another connection link and check Используйте приложение одной рукой. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Используйте этот адрес в профиле социальных сетей, на сайте или в подписи email. + No comment provided by engineer. + Use web port Использовать веб-порт @@ -9270,6 +9844,7 @@ To connect, please ask your contact to create another connection link and check Verify + Проверить relay test step @@ -9334,6 +9909,7 @@ To connect, please ask your contact to create another connection link and check Videos + Видео No comment provided by engineer. @@ -9393,12 +9969,19 @@ To connect, please ask your contact to create another connection link and check Wait + Подождать alert action Wait response + Ожидание ответа relay test step + + Waiting for channel owner to add relays. + Ожидает, когда владелец канала добавит релеи. + No comment provided by engineer. + Waiting for desktop... Ожидается подключение компьютера... @@ -9406,12 +9989,12 @@ To connect, please ask your contact to create another connection link and check Waiting for file - Ожидается прием файла + Ожидается приём файла No comment provided by engineer. Waiting for image - Ожидается прием изображения + Ожидается приём изображения No comment provided by engineer. @@ -9431,17 +10014,22 @@ To connect, please ask your contact to create another connection link and check Warning: starting chat on multiple devices is not supported and will cause message delivery failures - Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений + Внимание: запуск чата на нескольких устройствах не поддерживается и приведёт к сбоям доставки сообщений No comment provided by engineer. Warning: you may lose some data! - Предупреждение: Вы можете потерять какие то данные! + Предупреждение: Вы можете потерять некоторые данные! + No comment provided by engineer. + + + We made connecting simpler for new users. + Мы упростили подключение для новых пользователей. No comment provided by engineer. WebRTC ICE servers - WebRTC ICE серверы + WebRTC ICE-серверы No comment provided by engineer. @@ -9466,7 +10054,7 @@ To connect, please ask your contact to create another connection link and check What's new - Новые функции + Что нового No comment provided by engineer. @@ -9486,7 +10074,12 @@ To connect, please ask your contact to create another connection link and check When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. - Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом. + Когда Вы соединены с контактом инкогнито, тот же самый профиль инкогнито будет использоваться для групп с этим контактом. + No comment provided by engineer. + + + Why SimpleX is built. + Зачем создан SimpleX. No comment provided by engineer. @@ -9521,12 +10114,12 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to file servers. - Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов. + Без Tor или VPN, Ваш IP-адрес будет доступен серверам файлов. No comment provided by engineer. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@. + Без Тора или ВПН, Ваш IP-адрес будет доступен этим серверам файлов: %@. alert message @@ -9541,7 +10134,7 @@ To connect, please ask your contact to create another connection link and check Wrong key or unknown file chunk address - most likely file is deleted. - Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удален. + Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удалён. file error text @@ -9551,7 +10144,7 @@ To connect, please ask your contact to create another connection link and check XFTP server - XFTP сервер + XFTP-сервер No comment provided by engineer. @@ -9618,7 +10211,7 @@ Repeat join request? You are connected to the server used to receive messages from this connection. - Вы подключены к серверу, используемому для приема сообщений от этого соединения. + Вы подключены к серверу, используемому для приёма сообщений от этого соединения. subscription status explanation @@ -9628,7 +10221,7 @@ Repeat join request? You are not connected to the server used to receive messages from this connection (no subscription). - Вы не подключены к серверу, используемому для получения сообщений по этому соединению (нет подписки). + Вы не подключены к серверу, через который Вы получали сообщения от этого контакта (нет подписки). subscription status explanation @@ -9668,7 +10261,7 @@ Repeat join request? You can give another try. - Вы можете попробовать еще раз. + Вы можете попробовать ещё раз. No comment provided by engineer. @@ -9703,11 +10296,12 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the channel. + Вы можете поделиться ссылкой или QR-кодом - любой сможет вступить в канал. No comment provided by engineer. You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. + Вы можете поделиться ссылкой или QR-кодом - любой сможет присоединиться к группе. Члены группы останутся, даже если вы позже удалите ссылку. No comment provided by engineer. @@ -9717,7 +10311,7 @@ Repeat join request? You can start chat via app Settings / Database or by restarting the app - Вы можете запустить чат через Настройки приложения или перезапустив приложение. + Вы можете запустить чат через Настройки приложения или перезапустив приложение No comment provided by engineer. @@ -9750,18 +10344,23 @@ Repeat join request? Вы не можете отправлять сообщения! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + Вы обязуетесь: +- Только законный контент в публичных группах +- Уважать других пользователей - без спама + No comment provided by engineer. + You connected to the channel via this relay link. + Вы подключились к каналу через эту ссылку релея. No comment provided by engineer. You could not be verified; please try again. - Верификация не удалась; пожалуйста, попробуйте ещё раз. - No comment provided by engineer. - - - You decide who can connect. - Вы определяете, кто может соединиться. + Ошибка аутентификации; попробуйте ещё раз. No comment provided by engineer. @@ -9773,7 +10372,7 @@ Repeat connection request? You have to enter passphrase every time the app starts - it is not stored on the device. - Пароль не сохранен на устройстве — Вы будете должны ввести его при каждом запуске чата. + Пароль не сохранён на устройстве - Вы будете должны ввести его при каждом запуске чата. No comment provided by engineer. @@ -9788,7 +10387,7 @@ Repeat connection request? You joined this group. Connecting to inviting group member. - Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы. + Вы вступили в группу. Устанавливается соединение с пригласившим Вас членом группы. No comment provided by engineer. @@ -9803,7 +10402,7 @@ Repeat connection request? 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. - Вы должны всегда использовать самую новую версию данных чата, ТОЛЬКО на одном устройстве, иначе Вы можете перестать получать сообщения от каких то контактов. + Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе Вы можете перестать получать сообщения от некоторых контактов. No comment provided by engineer. @@ -9831,6 +10430,11 @@ Repeat connection request? Вы должны получать уведомления. token info + + You were born without an account + Вы родились без аккаунта + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Вы сможете отправлять сообщения **только после того как Ваш запрос будет принят**. @@ -9863,11 +10467,12 @@ Repeat connection request? You will still receive calls and notifications from muted profiles when they are active. - Вы все равно получите звонки и уведомления в профилях без звука, когда они активные. + Вы всё равно получите звонки и уведомления в профилях без звука, когда они активные. No comment provided by engineer. You will stop receiving messages from this channel. Chat history will be preserved. + Вы перестанете получать сообщения из этого канала. История чата сохранится. No comment provided by engineer. @@ -9882,22 +10487,22 @@ Repeat connection request? You won't lose your contacts if you later delete your address. - Вы сможете удалить адрес, сохранив контакты, которые через него соединились. + Вы не потеряете контакты, если позже удалите Ваш адрес. No comment provided by engineer. You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile - Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль + Вы пытаетесь пригласить контакт, который знает Ваш профиль инкогнито, в группу, где Вы используете основной профиль 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 ICE servers - Ваши ICE серверы + Ваши ICE-серверы No comment provided by engineer. @@ -9907,7 +10512,7 @@ Repeat connection request? Your business contact - Ваш бизнес контакт + Ваш бизнес-контакт No comment provided by engineer. @@ -9917,6 +10522,7 @@ Repeat connection request? Your channel + Ваш канал No comment provided by engineer. @@ -9969,9 +10575,14 @@ Repeat connection request? Ваши контакты сохранятся. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Ваши разговоры принадлежат вам, как это всегда было до интернета. Сеть - это не место, куда вы приходите. Это место, которое вы создаёте и которым владеете. И никто не может это у вас отнять, делаете ли вы его конфиденциальным или публичным. + No comment provided by engineer. + Your credentials may be sent unencrypted. - Ваши учетные данные могут быть отправлены в незашифрованном виде. + Ваши учётные данные могут быть отправлены в незашифрованном виде. No comment provided by engineer. @@ -9989,6 +10600,11 @@ Repeat connection request? Ваша группа No comment provided by engineer. + + Your network + Ваша сеть + No comment provided by engineer. + Your preferences Ваши предпочтения @@ -10007,6 +10623,8 @@ Repeat connection request? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + Ваш профиль **%@** будет отправлен чат-релеям и подписчикам канала. +Релеи могут видеть сообщения канала. No comment provided by engineer. @@ -10021,14 +10639,19 @@ Relays can access channel messages. Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. - Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю. + Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. Серверы SimpleX не могут получить доступ к Вашему профилю. No comment provided by engineer. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. - Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам. + Ваш профиль был изменен. Если вы сохраните его, обновлённый профиль будет отправлен всем вашим контактам. alert message + + Your public address + Ваш публичный адрес + No comment provided by engineer. + Your random profile Случайный профиль @@ -10036,10 +10659,12 @@ Relays can access channel messages. Your relay address + Ваш адрес релея No comment provided by engineer. Your relay name + Ваше имя релея No comment provided by engineer. @@ -10057,21 +10682,11 @@ Relays can access channel messages. Настройки No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Внести свой вклад](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Отправить email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Поставить звездочку в GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_курсив_ @@ -10089,6 +10704,7 @@ Relays can access channel messages. accepted + принят(а) No comment provided by engineer. @@ -10113,6 +10729,7 @@ Relays can access channel messages. active + активный No comment provided by engineer. @@ -10182,7 +10799,7 @@ Relays can access channel messages. bad message hash - ошибка хэш сообщения + ошибка хэша сообщения integrity error chat item @@ -10226,6 +10843,11 @@ marked deleted chat item preview text входящий звонок… call status + + can't broadcast + нельзя публиковать + No comment provided by engineer. + can't send messages нельзя отправлять @@ -10263,10 +10885,12 @@ marked deleted chat item preview text channel + канал shown as sender role for channel messages channel profile updated + профиль канала обновлён snd group event chat item @@ -10286,7 +10910,7 @@ marked deleted chat item preview text connected - соединение установлено + соединен(а) No comment provided by engineer. @@ -10341,7 +10965,7 @@ marked deleted chat item preview text contact deleted - контакт удален + контакт удалён No comment provided by engineer. @@ -10417,6 +11041,7 @@ pref value deleted channel + удалил(а) канал rcv group event chat item @@ -10441,7 +11066,7 @@ pref value disabled - выключено + выключен No comment provided by engineer. @@ -10531,6 +11156,7 @@ pref value error: %@ + ошибка: %@ receive error chat item @@ -10540,6 +11166,7 @@ pref value failed + ошибка No comment provided by engineer. @@ -10564,7 +11191,7 @@ pref value group profile updated - профиль группы обновлен + профиль группы обновлён snd group event chat item @@ -10579,7 +11206,7 @@ pref value iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications. - Пароль базы данных будет безопасно сохранен в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления. + Пароль базы данных будет безопасно сохранён в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления. No comment provided by engineer. @@ -10664,6 +11291,7 @@ pref value link + ссылка No comment provided by engineer. @@ -10688,7 +11316,7 @@ pref value member has old version - член имеет старую версию + член группы имеет старую версию No comment provided by engineer. @@ -10738,6 +11366,7 @@ pref value new + новый No comment provided by engineer. @@ -10865,6 +11494,7 @@ time to disappear relay + релей member role @@ -10879,8 +11509,14 @@ time to disappear removed (%d attempts) + удалено (%d попыток) receive error chat item + + removed by operator + удалено оператором + No comment provided by engineer. + removed contact address удалён адрес контакта @@ -10888,7 +11524,7 @@ time to disappear removed from group - удален из группы + удалён из группы No comment provided by engineer. @@ -10992,7 +11628,7 @@ last received msg: %2$@ standard end-to-end encryption - стандартное end-to-end шифрование + стандартное сквозное шифрование chat item text @@ -11037,6 +11673,7 @@ last received msg: %2$@ updated channel profile + обновлён профиль канала rcv group event chat item @@ -11061,6 +11698,7 @@ last received msg: %2$@ via %@ + через %@ relay hostname @@ -11080,7 +11718,7 @@ last received msg: %2$@ via relay - через relay сервер + через релей-сервер No comment provided by engineer. @@ -11130,7 +11768,7 @@ last received msg: %2$@ you accepted this member - Вы приняли этого члена + Вы приняли этого члена группы snd group event chat item @@ -11140,6 +11778,7 @@ last received msg: %2$@ you are subscriber + Вы подписчик No comment provided by engineer. @@ -11202,6 +11841,11 @@ last received msg: %2$@ \~зачеркнуть~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Ошибка проверки подписи: %@. + owner verification + @@ -11374,7 +12018,7 @@ last received msg: %2$@ Database passphrase is different from saved in the keychain. - Пароль базы данных отличается от сохраненного в keychain. + Пароль базы данных отличается от сохранённого в keychain. No comment provided by engineer. @@ -11454,7 +12098,7 @@ last received msg: %2$@ Please create a profile in the SimpleX app - Пожалуйста, создайте профиль в приложении SimpleX. + Пожалуйста, создайте профиль в приложении SimpleX No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 6ffea9646d..04c51bbf43 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -167,9 +167,20 @@ %d เดือน time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -203,10 +214,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -214,7 +233,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -324,10 +351,18 @@ channel relay bar progress with errors %u ข้อความที่ถูกข้าม No comment provided by engineer. + + (from owner) + chat link info line + (new) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) No comment provided by engineer. @@ -411,6 +446,12 @@ channel relay bar progress with errors - และอื่น ๆ! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -502,6 +543,10 @@ time interval อีกสองสามอย่าง No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact ผู้ติดต่อใหม่ @@ -614,9 +659,8 @@ swipe action Active connections 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. - เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -676,6 +720,10 @@ swipe action Added message servers No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -781,6 +829,14 @@ swipe action All profiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -835,6 +891,10 @@ swipe action อนุญาตให้ลบข้อความแบบถาวรเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตให้คุณเท่านั้น No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. อนุญาตการแสดงปฏิกิริยาต่อข้อความเฉพาะเมื่อผู้ติดต่อของคุณอนุญาตเท่านั้น @@ -850,6 +910,10 @@ swipe action อนุญาตการส่งข้อความโดยตรงไปยังสมาชิก No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. อนุญาตให้ส่งข้อความที่จะหายไปหลังปิดแชท (disappearing message) @@ -859,6 +923,10 @@ swipe action Allow sharing No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) อนุญาตให้ลบข้อความที่ส่งไปแล้วอย่างถาวร @@ -957,11 +1025,6 @@ swipe action รับสาย No comment provided by engineer. - - Anybody can host servers. - โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ - No comment provided by engineer. - App build: %@ รุ่นแอป: %@ @@ -1148,6 +1211,19 @@ swipe action แฮชข้อความไม่ดี No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls No comment provided by engineer. @@ -1274,6 +1350,10 @@ swipe action ทั้งคุณและผู้ติดต่อของคุณสามารถส่งข้อความเสียงได้ No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1284,7 +1364,7 @@ swipe action Business address - No comment provided by engineer. + chat link info line Business chats @@ -1303,12 +1383,6 @@ swipe action ตามโปรไฟล์แชท (ค่าเริ่มต้น) หรือ [โดยการเชื่อมต่อ](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (เบต้า) No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! สิ้นสุดการโทรแล้ว! @@ -1457,12 +1531,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1477,6 +1560,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1489,6 +1576,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat No comment provided by engineer. @@ -1595,7 +1686,8 @@ set passcode view Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1610,10 +1702,22 @@ set passcode view แชท No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1761,10 +1865,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm ยืนยัน @@ -1855,6 +1955,10 @@ This is your own one-time link! เชื่อมต่อผ่านลิงก์ new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link new chat sheet title @@ -1922,7 +2026,7 @@ This is your own one-time link! Connection error (AUTH) การเชื่อมต่อผิดพลาด (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -1971,6 +2075,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows ผู้ติดต่ออนุญาต @@ -2036,6 +2144,11 @@ This is your own one-time link! ดำเนินการต่อ No comment provided by engineer. + + Contribute + มีส่วนร่วม + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2062,11 +2175,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - สร้าง - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2128,11 +2236,19 @@ This is your own one-time link! Create your address No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile สร้างโปรไฟล์ของคุณ No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2304,11 +2420,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - กระจายอำนาจแล้ว - No comment provided by engineer. - Decode link relay test step @@ -2675,6 +2786,14 @@ alert button ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) ปิดใช้งาน (เก็บการแทนที่) @@ -2771,6 +2890,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -2859,6 +2982,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit แก้ไข @@ -2880,7 +3007,7 @@ chat item action Enable เปิดใช้งาน - No comment provided by engineer. + alert button Enable (keep overrides) @@ -2914,6 +3041,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -2932,16 +3063,15 @@ chat item action เปิดใช้งานการแจ้งเตือนทันที? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock เปิดใช้งานการล็อค No comment provided by engineer. - - Enable notifications - เปิดใช้งานการแจ้งเตือน - No comment provided by engineer. - Enable periodic notifications? เปิดใช้การแจ้งเตือนเป็นระยะๆ ไหม? @@ -3066,6 +3196,10 @@ chat item action ใส่รหัสผ่านด้านบนเพื่อแสดง! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3096,7 +3230,7 @@ chat item action Error ผิดพลาด - No comment provided by engineer. + conn error description Error aborting address change @@ -3407,6 +3541,10 @@ chat item action เกิดข้อผิดพลาดในการตั้งค่าใบตอบรับการจัดส่ง! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat เกิดข้อผิดพลาดในการเริ่มแชท @@ -3740,6 +3878,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -3870,6 +4012,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -3924,7 +4070,7 @@ Error: %2$@ Group link ลิงค์กลุ่ม - No comment provided by engineer. + chat link info line Group links @@ -4032,6 +4178,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works วิธีการ SimpleX ทํางานอย่างไร @@ -4125,11 +4275,6 @@ Error: %2$@ โดยทันที No comment provided by engineer. - - Immune to spam - มีภูมิคุ้มกันต่อสแปมและการละเมิด - No comment provided by engineer. - Import นำเข้า @@ -4259,9 +4404,9 @@ More improvements are coming soon! บทบาทเริ่มต้น No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + ติดตั้ง SimpleX Chat สำหรับเทอร์มินัล No comment provided by engineer. @@ -4312,7 +4457,7 @@ More improvements are coming soon! Invalid connection link ลิงค์เชื่อมต่อไม่ถูกต้อง - No comment provided by engineer. + conn error description Invalid display name! @@ -4370,6 +4515,10 @@ More improvements are coming soon! เชิญสมาชิก No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4557,6 +4706,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat มาคุยกันใน SimpleX Chat @@ -4576,6 +4729,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -4743,6 +4900,10 @@ This is your link for group %@! สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร @@ -4889,6 +5050,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -4913,12 +5082,12 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device + + Migrate device No comment provided by engineer. @@ -5031,6 +5200,10 @@ This is your link for group %@! เครือข่ายและเซิร์ฟเวอร์ No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection No comment provided by engineer. @@ -5039,6 +5212,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5051,6 +5228,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings การตั้งค่าเครือข่าย @@ -5065,6 +5247,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode รหัสผ่านใหม่ @@ -5154,6 +5340,15 @@ This is your link for group %@! เลขที่ No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password ไม่มีรหัสผ่านสำหรับแอป @@ -5292,9 +5487,16 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5350,7 +5552,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5369,11 +5571,19 @@ new chat action ฐานข้อมูลเก่า No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link ลิงก์คำเชิญแบบใช้ครั้งเดียว No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5391,6 +5601,10 @@ Requires compatible VPN. โฮสต์หัวหอมจะไม่ถูกใช้ No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5487,7 +5701,8 @@ Requires compatible VPN. Open - alert action + alert action +alert button Open Settings @@ -5520,6 +5735,10 @@ Requires compatible VPN. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -5576,6 +5795,13 @@ Requires compatible VPN. Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -5592,6 +5818,10 @@ Requires compatible VPN. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5600,6 +5830,10 @@ Requires compatible VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5621,6 +5855,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count จํานวน PING @@ -5674,6 +5912,10 @@ Requires compatible VPN. แปะภาพ No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5851,13 +6093,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - นิยามความเป็นส่วนตัวใหม่ + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -5920,9 +6161,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ + + Profile update will be sent to your SimpleX contacts. alert message @@ -5930,6 +6170,10 @@ Error: %@ ห้ามการโทรด้วยเสียง/วิดีโอ No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. ห้ามการลบข้อความที่ย้อนกลับไม่ได้ @@ -5958,6 +6202,10 @@ Error: %@ ห้ามส่งข้อความโดยตรงถึงสมาชิก No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. ห้ามส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) @@ -6018,6 +6266,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications การแจ้งเตือนแบบทันที @@ -6055,23 +6307,14 @@ Enable in *Network & servers* settings. อ่านเพิ่มเติม No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + อ่านเพิ่มเติมในคู่มือผู้ใช้ No comment provided by engineer. - - 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) - 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) + + Read more in our GitHub repository. + อ่านเพิ่มเติมในพื้นที่เก็บข้อมูล GitHub No comment provided by engineer. @@ -6230,6 +6473,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. ใช้เซิร์ฟเวอร์รีเลย์ในกรณีที่จำเป็นเท่านั้น บุคคลอื่นสามารถสังเกตที่อยู่ IP ของคุณได้ @@ -6244,6 +6491,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove ลบ @@ -6496,6 +6747,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6537,6 +6792,10 @@ chat item action บันทึกและแจ้งให้สมาชิกในกลุ่มทราบ No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6715,6 +6974,10 @@ chat item action รหัสความปลอดภัย No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select เลือก @@ -6833,6 +7096,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. ส่งจากแกลเลอรีหรือแป้นพิมพ์แบบกำหนดเอง @@ -6842,6 +7109,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -6856,6 +7127,10 @@ chat item action ผู้ส่งอาจลบคําขอการเชื่อมต่อแล้ว No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. การส่งใบเสร็จรับการจัดส่งข้อความจะถูกเปิดในโปรไฟล์แชทที่มองเห็นได้ทั้งหมด @@ -7087,6 +7362,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -7119,11 +7402,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - แชร์ที่อยู่กับผู้ติดต่อ? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7157,9 +7443,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - แชร์กับผู้ติดต่อ + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7380,6 +7669,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + ติดดาวบน GitHub + No comment provided by engineer. + Start chat เริ่มแชท @@ -7476,6 +7770,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7484,6 +7782,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -7561,6 +7895,10 @@ Relay address was used to set up this relay for the channel. ถ่ายภาพ No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7573,10 +7911,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7609,6 +7943,10 @@ Relay address was used to set up this relay for the channel. แตะเพื่อเข้าร่วมโหมดไม่ระบุตัวตน No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7703,6 +8041,10 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -7727,9 +8069,9 @@ It can happen because of some bug or when the connection is compromised.encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้! No comment provided by engineer. - - The future of messaging - การส่งข้อความส่วนตัวรุ่นต่อไป + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -7764,6 +8106,10 @@ It can happen because of some bug or when the connection is compromised.ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้ No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -7803,6 +8149,14 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -7911,6 +8265,10 @@ It can happen because of some bug or when the connection is compromised.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection เพื่อสร้างการเชื่อมต่อใหม่ @@ -7989,10 +8347,6 @@ You will be prompted to complete authentication before this feature is enabled.< ในการตรวจสอบการเข้ารหัสแบบ encrypt จากต้นจนจบ กับผู้ติดต่อของคุณ ให้เปรียบเทียบ (หรือสแกน) รหัสบนอุปกรณ์ของคุณ No comment provided by engineer. - - Toggle chat list: - No comment provided by engineer. - Toggle incognito when connecting. No comment provided by engineer. @@ -8005,6 +8359,10 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total No comment provided by engineer. @@ -8160,12 +8518,16 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update อัปเดต @@ -8274,11 +8636,6 @@ To connect, please ask your contact to create another connection link and check Use TCP port 443 for preset servers only. No comment provided by engineer. - - Use chat - ใช้แชท - No comment provided by engineer. - Use current profile new chat action @@ -8350,6 +8707,10 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8488,6 +8849,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8524,6 +8889,10 @@ To connect, please ask your contact to create another connection link and check คำเตือน: คุณอาจสูญเสียข้อมูลบางส่วน! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers เซิร์ฟเวอร์ WebRTC ICE @@ -8570,6 +8939,10 @@ To connect, please ask your contact to create another connection link and check เมื่อคุณแชร์โปรไฟล์ที่ไม่ระบุตัวตนกับใครสักคน โปรไฟล์นี้จะใช้สำหรับกลุ่มที่พวกเขาเชิญคุณ No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -8800,6 +9173,12 @@ Repeat join request? คุณไม่สามารถส่งข้อความได้! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -8809,11 +9188,6 @@ Repeat join request? เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง. No comment provided by engineer. - - You decide who can connect. - ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -8874,6 +9248,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9004,6 +9382,10 @@ Repeat connection request? ผู้ติดต่อของคุณจะยังคงเชื่อมต่ออยู่ No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9022,6 +9404,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences การตั้งค่าของคุณ @@ -9059,6 +9445,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile โปรไฟล์แบบสุ่มของคุณ @@ -9086,21 +9476,11 @@ Relays can access channel messages. การตั้งค่าของคุณ No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [มีส่วนร่วม](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [ส่งอีเมลถึงเรา](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [ติดดาวบน GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_ตัวเอียง_ @@ -9241,6 +9621,10 @@ marked deleted chat item preview text กำลังโทร… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -9866,6 +10250,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10156,6 +10544,10 @@ last received msg: %2$@ \~ตี~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index f8890ca077..90d537d06c 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -185,9 +185,20 @@ %d ay time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u mesajlar atlandı. No comment provided by engineer. + + (from owner) + chat link info line + (new) (yeni) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (bu cihaz v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - ve fazlası! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Birkaç şey daha No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Yeni kişi @@ -670,9 +715,8 @@ swipe action Aktif bağlantılar 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. - Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Mesaj sunucuları eklendi No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Ek ana renk @@ -859,6 +907,14 @@ swipe action Tüm Profiller profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Tüm raporlar sizin için arşivlenecek. @@ -919,6 +975,10 @@ swipe action Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Yalnızca kişin mesaj tepkilerine izin veriyorsa sen de ver. @@ -934,6 +994,10 @@ swipe action Üyelere doğrudan mesaj göndermeye izin ver. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Kendiliğinden yok olan mesajlar göndermeye izin ver. @@ -944,6 +1008,10 @@ swipe action Paylaşıma izin ver No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde) @@ -1049,11 +1117,6 @@ swipe action Aramayı cevapla No comment provided by engineer. - - Anybody can host servers. - Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir. - No comment provided by engineer. - App build: %@ Uygulama sürümü: %@ @@ -1258,6 +1321,19 @@ swipe action Kötü mesaj karması No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Daha iyi aramalar @@ -1407,6 +1483,10 @@ swipe action Sen ve konuştuğun kişi sesli mesaj gönderebilir. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1419,7 +1499,7 @@ swipe action Business address İş adresi - No comment provided by engineer. + chat link info line Business chats @@ -1441,15 +1521,6 @@ swipe action Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz: -- herkese açık gruplarda yalnızca yasal içerik göndermek. -- diğer kullanıcılara saygı göstermek – spam yapmamak. - No comment provided by engineer. - Call already ended! Arama çoktan bitti! @@ -1610,12 +1681,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1630,6 +1710,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1642,6 +1726,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Sohbet @@ -1761,7 +1849,8 @@ set passcode view Chat with admins Yöneticilerle sohbet et - chat toolbar + chat feature +chat toolbar Chat with member @@ -1778,11 +1867,23 @@ set passcode view Sohbetler No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Üyelerle sohbetler No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Her 20 dakikada mesajları kontrol et. @@ -1950,11 +2051,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Sunucu operatörlerini yapılandır - No comment provided by engineer. - Confirm Onayla @@ -2060,6 +2156,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı aracılığıyla bağlan new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Tek kullanımlık bağlantı aracılığıyla bağlan @@ -2138,7 +2238,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connection error (AUTH) Bağlantı hatası (DOĞRULAMA) - No comment provided by engineer. + conn error description Connection failed @@ -2196,6 +2296,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantılar No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kişi izin veriyor @@ -2266,6 +2370,11 @@ Bu senin kendi tek kullanımlık bağlantın! Devam et No comment provided by engineer. + + Contribute + Katkıda bulun + No comment provided by engineer. + Conversation deleted! Sohbet silindi! @@ -2296,11 +2405,6 @@ Bu senin kendi tek kullanımlık bağlantın! İsim %@ olarak düzeltilsin mi? alert message - - Create - Oluştur - No comment provided by engineer. - Create 1-time link Tek kullanımlık bağlantı oluştur @@ -2369,11 +2473,19 @@ Bu senin kendi tek kullanımlık bağlantın! Adresinizi oluşturun No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Profilini oluştur No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Yaratıldı @@ -2556,11 +2668,6 @@ Bu senin kendi tek kullanımlık bağlantın! Hata ayıklama teslimatı No comment provided by engineer. - - Decentralized - Merkezi Olmayan - No comment provided by engineer. - Decode link relay test step @@ -2957,6 +3064,14 @@ alert button Bu grupta üyeler arasında direkt mesajlaşma yasaktır. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Devre dışı bırak (geçersiz kılmaları koru) @@ -3062,6 +3177,10 @@ alert button Yeni üyelere geçmişi gönderme. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Kimlik bilgilerini proxy ile kullanmayın. @@ -3163,6 +3282,10 @@ chat item action Uçtan uca şifrelenmiş bildirimler. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Düzenle @@ -3185,7 +3308,7 @@ chat item action Enable Etkinleştir - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3221,6 +3344,10 @@ chat item action Kamera erişimini etkinleştir No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Varsayılan olarak kaybolan mesajları etkinleştirin. @@ -3241,16 +3368,15 @@ chat item action Anlık bildirimler etkinleştirilsin mi? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Kilidi etkinleştir No comment provided by engineer. - - Enable notifications - Bildirimleri etkinleştir - No comment provided by engineer. - Enable periodic notifications? Periyodik bildirimler etkinleştirilsin mi? @@ -3385,6 +3511,10 @@ chat item action Göstermek için yukarıdaki şifreyi gir! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3417,7 +3547,7 @@ chat item action Error Hata - No comment provided by engineer. + conn error description Error aborting address change @@ -3760,6 +3890,10 @@ chat item action Görüldü ayarlanırken hata oluştu! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Sohbet başlatılırken hata oluştu @@ -4123,6 +4257,10 @@ server test error Tüm moderatörler için No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Sohbet profili için %@: @@ -4277,6 +4415,10 @@ Hata: %2$@ Bahsedildiğinde bildirim alın. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! İyi öğlenler! @@ -4335,7 +4477,7 @@ Hata: %2$@ Group link Grup bağlantısı - No comment provided by engineer. + chat link info line Group links @@ -4447,6 +4589,10 @@ Hata: %2$@ Yeni üyelere geçmiş gönderilmedi. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works SimpleX nasıl çalışır @@ -4545,11 +4691,6 @@ Hata: %2$@ Hemen No comment provided by engineer. - - Immune to spam - Spam ve kötüye kullanıma karşı bağışıklı - No comment provided by engineer. - Import İçe aktar @@ -4692,9 +4833,9 @@ Daha fazla iyileştirme yakında geliyor! Başlangıç rolü No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - [Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Terminal için SimpleX Chat'i indir No comment provided by engineer. @@ -4752,7 +4893,7 @@ Daha fazla iyileştirme yakında geliyor! Invalid connection link Geçersiz bağlanma bağlantısı - No comment provided by engineer. + conn error description Invalid display name! @@ -4816,6 +4957,10 @@ Daha fazla iyileştirme yakında geliyor! Üyeleri davet et No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Sohbete davet et @@ -5016,6 +5161,10 @@ Bu senin grup için bağlantın %@! Mobil ağlarda daha az trafik. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Hadi SimpleX Chat'te konuşalım @@ -5036,6 +5185,10 @@ Bu senin grup için bağlantın %@! Telefon ve bilgisayar uygulamalarını bağla! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Bağlanmış bilgisayar ayarları @@ -5219,6 +5372,10 @@ Bu senin grup için bağlantın %@! Grup üyeleri mesaj tepkileri ekleyebilir. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) @@ -5383,6 +5540,14 @@ Bu senin grup için bağlantın %@! %@ den gelen mesajlar gösterilecektir! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Bu sohbetteki mesajlar asla silinmeyecek. @@ -5413,16 +5578,15 @@ Bu senin grup için bağlantın %@! Mesajlar, dosyalar ve aramalar **kuantum dirençli e2e şifreleme** ile mükemmel ileri gizlilik, inkar ve zorla girme kurtarma ile korunur. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Cihazı taşıma No comment provided by engineer. - - Migrate from another device - Başka bir cihazdan geçiş yapın - No comment provided by engineer. - Migrate here Buraya göç edin @@ -5543,6 +5707,10 @@ Bu senin grup için bağlantın %@! Ağ & sunucular No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection Ağ bağlantısı @@ -5553,6 +5721,10 @@ Bu senin grup için bağlantın %@! Ağ merkeziyetsizliği No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu. @@ -5568,6 +5740,11 @@ Bu senin grup için bağlantın %@! Ağ operatörü No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Ağ ayarları @@ -5583,6 +5760,10 @@ Bu senin grup için bağlantın %@! Yeni token status text + + New 1-time link + No comment provided by engineer. + New Passcode Yeni şifre @@ -5682,6 +5863,15 @@ Bu senin grup için bağlantın %@! Hayır No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Uygulama şifresi yok @@ -5840,9 +6030,16 @@ Bu senin grup için bağlantın %@! Okunmamış sohbet yok No comment provided by engineer. - - No user identifiers. - Herhangi bir kullanıcı tanımlayıcısı yok. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5906,7 +6103,7 @@ Bu senin grup için bağlantın %@! OK TAMAM - No comment provided by engineer. + alert button Off @@ -5925,11 +6122,19 @@ new chat action Eski veritabanı No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Tek zamanlı bağlantı daveti No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5949,6 +6154,10 @@ VPN'nin etkinleştirilmesi gerekir. Onion ana bilgisayarları kullanılmayacaktır. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Yalnızca sohbet sahipleri tercihleri değiştirebilir. @@ -6052,7 +6261,8 @@ VPN'nin etkinleştirilmesi gerekir. Open - alert action + alert action +alert button Open Settings @@ -6088,6 +6298,10 @@ VPN'nin etkinleştirilmesi gerekir. Açık koşullar No comment provided by engineer. + + Open external link? + alert title + Open full link Tam linki aç @@ -6157,6 +6371,13 @@ VPN'nin etkinleştirilmesi gerekir. Operatör sunucusu alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Veya arşiv dosyasını içe aktar @@ -6177,6 +6398,10 @@ VPN'nin etkinleştirilmesi gerekir. Veya bu dosya bağlantısını güvenli bir şekilde paylaşın No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Veya bu kodu göster @@ -6187,6 +6412,10 @@ VPN'nin etkinleştirilmesi gerekir. Veya özel olarak paylaşmak için No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Sohbetleri listelere ayır @@ -6212,6 +6441,10 @@ VPN'nin etkinleştirilmesi gerekir. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING sayısı @@ -6267,6 +6500,10 @@ VPN'nin etkinleştirilmesi gerekir. Fotoğraf yapıştır No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Bağlanmak için bağlantıyı yapıştır! @@ -6464,14 +6701,12 @@ Hata: %@ Gizlilik politikası ve kullanım koşulları. No comment provided by engineer. - - Privacy redefined - Gizlilik yeniden tanımlandı + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez. + + Private and secure messaging. No comment provided by engineer. @@ -6543,9 +6778,8 @@ Hata: %@ Profil teması No comment provided by engineer. - - Profile update will be sent to your contacts. - Profil güncellemesi kişilerinize gönderilecektir. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6553,6 +6787,10 @@ Hata: %@ Sesli/görüntülü aramaları yasakla. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Geri dönüşsüz mesaj silme işlemini yasakla. @@ -6583,6 +6821,10 @@ Hata: %@ Üyelere doğrudan mesaj göndermeyi yasakla. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Kaybolan mesajların gönderimini yasakla. @@ -6650,6 +6892,10 @@ Enable in *Network & servers* settings. Proxy şifre gerektirir No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Anında bildirimler @@ -6690,24 +6936,14 @@ Enable in *Network & servers* settings. Dahasını oku No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Kullanıcı Rehberinde daha fazlasını okuyun. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(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). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - [GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + GitHub deposunda daha fazlasını okuyun. No comment provided by engineer. @@ -6883,6 +7119,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Yönlendirici sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir. @@ -6897,6 +7137,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Sil @@ -7179,6 +7423,10 @@ swipe action SOCKS vekili No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Dosyaları güvenle alın @@ -7224,6 +7472,10 @@ chat item action Kaydet ve grup üyelerine bildir No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Kayıt et ve yeniden bağlan @@ -7417,6 +7669,10 @@ chat item action Güvenlik kodu No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Seç @@ -7547,6 +7803,10 @@ chat item action Mesaj olmadan istek gönder No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Bunları galeriden veya özel klavyelerden gönder. @@ -7557,6 +7817,10 @@ chat item action Yeni üyelere 100 adete kadar son mesajları gönderin. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Özel geri bildiriminizi gruplara gönderin. @@ -7572,6 +7836,10 @@ chat item action Gönderici bağlantı isteğini silmiş olabilir. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir. @@ -7831,6 +8099,14 @@ chat item action Ayarlar değiştirildi. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Profil resimlerini şekillendir @@ -7867,11 +8143,14 @@ chat item action Adresinizi herkese açık olarak paylaşın No comment provided by engineer. - - Share address with contacts? - Kişilerle adres paylaşılsın mı? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Diğer uygulamalardan paylaşın. @@ -7911,9 +8190,12 @@ chat item action SimpleX ile paylaş No comment provided by engineer. - - Share with contacts - Kişilerle paylaş + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8163,6 +8445,11 @@ report reason Kare,daire, veya aralarında herhangi bir şey. No comment provided by engineer. + + Star on GitHub + Bize GitHub'da yıldız verin + No comment provided by engineer. + Start chat Sohbeti başlat @@ -8267,6 +8554,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8275,6 +8566,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8360,6 +8687,10 @@ Relay address was used to set up this relay for the channel. Fotoğraf çek No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Sohbet etmek için Bağlan'a dokunun @@ -8375,11 +8706,6 @@ Relay address was used to set up this relay for the channel. Botu kullanmak için Bağlan tuşuna bas No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8414,6 +8740,10 @@ Relay address was used to set up this relay for the channel. Gizli katılmak için tıkla No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Bağlantıyı yapıştırmak için tıkla @@ -8515,6 +8845,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Taradığınız kod bir SimpleX bağlantı QR kodu değildir. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Bağlantı, teslim edilmemiş mesajlar limitine ulaştı, kişiniz çevrimdışı olabilir. @@ -8540,9 +8874,9 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir! No comment provided by engineer. - - The future of messaging - Gizli mesajlaşmanın yeni nesli + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8580,6 +8914,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Aynı koşullar operatör **%@** için de geçerli olacaktır. @@ -8625,6 +8963,14 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Temalar No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Bu koşullar ayrıca şunlar için de geçerli olacaktır: **%@**. @@ -8748,6 +9094,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. İstenmeyen mesajları gizlemek için. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Yeni bir bağlantı oluşturmak için @@ -8835,11 +9185,6 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın). No comment provided by engineer. - - Toggle chat list: - Sohbet listesini değiştir: - No comment provided by engineer. - Toggle incognito when connecting. Bağlanırken gizli moda geçiş yap. @@ -8855,6 +9200,10 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Araç çubuğu opaklığı No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total Toplam @@ -9023,13 +9372,17 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Unsupported connection link Desteklenmeyen bağlantı bağlantısı - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Yeni üyelere 100e kadar en son mesajlar gönderildi. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Güncelle @@ -9155,11 +9508,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sadece ön ayar sunucuları için TCP port 443 kullanın. No comment provided by engineer. - - Use chat - Sohbeti kullan - No comment provided by engineer. - Use current profile Şu anki profili kullan @@ -9243,6 +9591,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uygulamayı tek elle kullan. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Web portunu kullan @@ -9394,6 +9746,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Bilgisayar için bekleniyor... @@ -9434,6 +9790,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uyarı: Bazı verileri kaybedebilirsin! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE sunucuları @@ -9484,6 +9844,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Biriyle gizli bir profil paylaştığınızda, bu profil sizi davet ettikleri gruplar için kullanılacaktır. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9743,6 +10107,12 @@ Katılma isteği tekrarlansın mı? Mesajlar gönderemezsiniz! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9752,11 +10122,6 @@ Katılma isteği tekrarlansın mı? Doğrulanamadınız; lütfen tekrar deneyin. No comment provided by engineer. - - You decide who can connect. - Kimin bağlanabileceğine siz karar verirsiniz. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9824,6 +10189,10 @@ Bağlantı isteği tekrarlansın mı? Bildirim almanız gerekiyor. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Mesaj gönderebilmek için **isteğinizin kabul edilmesini beklemelisiniz**. @@ -9962,6 +10331,10 @@ Bağlantı isteği tekrarlansın mı? Kişileriniz bağlı kalacaktır. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Kimlik bilgileriniz şifrelenmeden gönderilebilir. @@ -9982,6 +10355,10 @@ Bağlantı isteği tekrarlansın mı? Grubunuz No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Tercihleriniz @@ -10022,6 +10399,10 @@ Relays can access channel messages. Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir. alert message + + Your public address + No comment provided by engineer. + Your random profile Rasgele profiliniz @@ -10050,21 +10431,11 @@ Relays can access channel messages. Ayarlarınız No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Katkıda bulun](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Bize e-posta gönder](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Bize GitHub'da yıldız verin](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italik_ @@ -10219,6 +10590,10 @@ marked deleted chat item preview text aranıyor… call status + + can't broadcast + No comment provided by engineer. + can't send messages mesaj gönderilemiyor @@ -10873,6 +11248,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address kişi adresi silindi @@ -11194,6 +11573,10 @@ son alınan msj: %2$@ \~çizik~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 839b4e2652..c20b26e029 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -185,9 +185,20 @@ %d місяців time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u повідомлень пропущено. No comment provided by engineer. + + (from owner) + chat link info line + (new) (новий) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (цей пристрій v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - і багато іншого! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Ще кілька речей No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Новий контакт @@ -670,9 +715,8 @@ swipe action Активні з'єднання 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. - Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Додано сервери повідомлень No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Додатковий акцент @@ -859,6 +907,14 @@ swipe action Всі профілі profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Всі скарги будуть заархівовані для вас. @@ -918,6 +974,10 @@ swipe action Дозволяйте безповоротне видалення повідомлень, тільки якщо контакт дозволяє вам це зробити. (24 години) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Дозволяйте реакції на повідомлення, тільки якщо ваш контакт дозволяє їх. @@ -933,6 +993,10 @@ swipe action Дозволяє надсилати прямі повідомлення користувачам. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Дозволити надсилання зникаючих повідомлень. @@ -943,6 +1007,10 @@ swipe action Дозволити спільний доступ No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Дозволяє безповоротно видаляти надіслані повідомлення. (24 години) @@ -1047,11 +1115,6 @@ swipe action Відповісти на дзвінок No comment provided by engineer. - - Anybody can host servers. - Кожен може хостити сервери. - No comment provided by engineer. - App build: %@ Збірка програми: %@ @@ -1256,6 +1319,19 @@ swipe action Поганий хеш повідомлення No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Кращі дзвінки @@ -1403,6 +1479,10 @@ swipe action Надсилати голосові повідомлення можете як ви, так і ваш контакт. No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1415,7 +1495,7 @@ swipe action Business address Адреса підприємства - No comment provided by engineer. + chat link info line Business chats @@ -1437,15 +1517,6 @@ swipe action Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Використовуючи SimpleX Chat, ви погоджуєтеся: -- надсилати лише легальний контент у публічних групах. -- поважати інших користувачів - без спаму. - No comment provided by engineer. - Call already ended! Дзвінок вже закінчився! @@ -1606,12 +1677,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1626,6 +1706,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1638,6 +1722,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Чат @@ -1757,7 +1845,8 @@ set passcode view Chat with admins Чат з адміністраторами - chat toolbar + chat feature +chat toolbar Chat with member @@ -1774,11 +1863,23 @@ set passcode view Чати No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Чати з учасниками No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Перевіряйте повідомлення кожні 20 хв. @@ -1946,11 +2047,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - Налаштувати операторів сервера - No comment provided by engineer. - Confirm Підтвердити @@ -2056,6 +2152,10 @@ This is your own one-time link! Підключіться за посиланням new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Під'єднатися за одноразовим посиланням @@ -2134,7 +2234,7 @@ This is your own one-time link! Connection error (AUTH) Помилка підключення (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2192,6 +2292,10 @@ This is your own one-time link! З'єднання No comment provided by engineer. + + Contact address + chat link info line + Contact allows Контакт дозволяє @@ -2261,6 +2365,11 @@ This is your own one-time link! Продовжуйте No comment provided by engineer. + + Contribute + Внесок + No comment provided by engineer. + Conversation deleted! Розмова видалена! @@ -2291,11 +2400,6 @@ This is your own one-time link! Виправити ім'я на %@? alert message - - Create - Створити - No comment provided by engineer. - Create 1-time link Створити одноразове посилання @@ -2364,11 +2468,19 @@ This is your own one-time link! Створіть свою адресу No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile Створіть свій профіль No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Створено @@ -2551,11 +2663,6 @@ This is your own one-time link! Доставка налагодження No comment provided by engineer. - - Decentralized - Децентралізований - No comment provided by engineer. - Decode link relay test step @@ -2951,6 +3058,14 @@ alert button У цій групі заборонені прямі повідомлення між учасниками. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) Вимкнути (зберегти перевизначення) @@ -3056,6 +3171,10 @@ alert button Не надсилайте історію новим користувачам. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Не використовуйте облікові дані з проксі. @@ -3157,6 +3276,10 @@ chat item action Зашифровані сповіщення E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Редагувати @@ -3179,7 +3302,7 @@ chat item action Enable Увімкнути - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3215,6 +3338,10 @@ chat item action Увімкніть доступ до камери No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Увімкнути зникаючі повідомлення за замовчуванням. @@ -3235,16 +3362,15 @@ chat item action Увімкнути миттєві сповіщення? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Увімкнути блокування No comment provided by engineer. - - Enable notifications - Увімкнути сповіщення - No comment provided by engineer. - Enable periodic notifications? Увімкнути періодичні сповіщення? @@ -3379,6 +3505,10 @@ chat item action Введіть пароль вище, щоб показати! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3411,7 +3541,7 @@ chat item action Error Помилка - No comment provided by engineer. + conn error description Error aborting address change @@ -3753,6 +3883,10 @@ chat item action Помилка встановлення підтвердження доставлення! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Помилка запуску чату @@ -4115,6 +4249,10 @@ server test error Для всіх модераторів No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Для профілю чату %@: @@ -4269,6 +4407,10 @@ Error: %2$@ Отримуйте сповіщення, коли вас згадують. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Доброго дня! @@ -4327,7 +4469,7 @@ Error: %2$@ Group link Посилання на групу - No comment provided by engineer. + chat link info line Group links @@ -4439,6 +4581,10 @@ Error: %2$@ Історія не надсилається новим учасникам. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Як працює SimpleX @@ -4537,11 +4683,6 @@ Error: %2$@ Негайно No comment provided by engineer. - - Immune to spam - Імунітет до спаму та зловживань - No comment provided by engineer. - Import Імпорт @@ -4684,9 +4825,9 @@ More improvements are coming soon! Початкова роль No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Встановіть SimpleX Chat для терміналу No comment provided by engineer. @@ -4744,7 +4885,7 @@ More improvements are coming soon! Invalid connection link Неправильне посилання для підключення - No comment provided by engineer. + conn error description Invalid display name! @@ -4808,6 +4949,10 @@ More improvements are coming soon! Запросити учасників No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Запросити в чат @@ -5008,6 +5153,10 @@ This is your link for group %@! Менше трафіку в мобільних мережах. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Поговоримо в чаті SimpleX @@ -5028,6 +5177,10 @@ This is your link for group %@! Зв'яжіть мобільні та десктопні додатки! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Параметри пов'язаного робочого столу @@ -5209,6 +5362,10 @@ This is your link for group %@! Учасники групи можуть додавати реакції на повідомлення. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) @@ -5373,6 +5530,14 @@ This is your link for group %@! Повідомлення від %@ будуть показані! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Повідомлення в цьому чаті ніколи не будуть видалені. @@ -5403,16 +5568,15 @@ This is your link for group %@! Повідомлення, файли та дзвінки захищені **квантово-стійким шифруванням e2e** з ідеальною секретністю переадресації, відмовою та відновленням після злому. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Перенести пристрій No comment provided by engineer. - - Migrate from another device - Перехід з іншого пристрою - No comment provided by engineer. - Migrate here Мігруйте сюди @@ -5533,6 +5697,10 @@ This is your link for group %@! Мережа та сервери No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection Підключення до мережі @@ -5543,6 +5711,10 @@ This is your link for group %@! Децентралізація мережі No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його. @@ -5558,6 +5730,11 @@ This is your link for group %@! Мережевий оператор No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Налаштування мережі @@ -5573,6 +5750,10 @@ This is your link for group %@! Новий token status text + + New 1-time link + No comment provided by engineer. + New Passcode Новий пароль @@ -5672,6 +5853,15 @@ This is your link for group %@! Ні No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password Немає пароля програми @@ -5830,9 +6020,16 @@ This is your link for group %@! Немає непрочитаних чатів No comment provided by engineer. - - No user identifiers. - Ніяких ідентифікаторів користувачів. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. No comment provided by engineer. @@ -5896,7 +6093,7 @@ This is your link for group %@! OK ОК - No comment provided by engineer. + alert button Off @@ -5915,11 +6112,19 @@ new chat action Стара база даних No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link Посилання на одноразове запрошення No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5939,6 +6144,10 @@ Requires compatible VPN. Onion хости не будуть використовуватися. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Лише власники чату можуть змінювати налаштування. @@ -6040,7 +6249,8 @@ Requires compatible VPN. Open Відкрито - alert action + alert action +alert button Open Settings @@ -6075,6 +6285,10 @@ Requires compatible VPN. Відкриті умови No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6142,6 +6356,13 @@ Requires compatible VPN. Сервер оператора alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Або імпортуйте архівний файл @@ -6162,6 +6383,10 @@ Requires compatible VPN. Або безпечно поділіться цим посиланням на файл No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Або покажіть цей код @@ -6172,6 +6397,10 @@ Requires compatible VPN. Або поділитися приватно No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Організовуйте чати в списки @@ -6197,6 +6426,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Кількість PING @@ -6252,6 +6485,10 @@ Requires compatible VPN. Вставити зображення No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Вставте посилання для підключення! @@ -6449,14 +6686,12 @@ Error: %@ Політика конфіденційності та умови використання. No comment provided by engineer. - - Privacy redefined - Конфіденційність переглянута + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Приватні чати, групи та ваші контакти недоступні для операторів сервера. + + Private and secure messaging. No comment provided by engineer. @@ -6528,9 +6763,8 @@ Error: %@ Тема профілю No comment provided by engineer. - - Profile update will be sent to your contacts. - Оновлення профілю буде надіслано вашим контактам. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6538,6 +6772,10 @@ Error: %@ Заборонити аудіо/відеодзвінки. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Заборонити незворотне видалення повідомлень. @@ -6568,6 +6806,10 @@ Error: %@ Заборонити надсилати прямі повідомлення учасникам. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Заборонити надсилання зникаючих повідомлень. @@ -6635,6 +6877,10 @@ Enable in *Network & servers* settings. Проксі вимагає пароль No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push-сповіщення @@ -6675,24 +6921,14 @@ Enable in *Network & servers* settings. Читати далі No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Читайте більше в User Guide. No comment provided by engineer. - - 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). - 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). + + Read more in our GitHub repository. + Читайте більше в нашому GitHub репозиторії. No comment provided by engineer. @@ -6868,6 +7104,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Релейний сервер використовується тільки в разі потреби. Інша сторона може бачити вашу IP-адресу. @@ -6882,6 +7122,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Видалити @@ -7163,6 +7407,10 @@ swipe action Проксі SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Безпечне отримання файлів @@ -7208,6 +7456,10 @@ chat item action Зберегти та повідомити учасників групи No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Збережіть і підключіться знову @@ -7401,6 +7653,10 @@ chat item action Код безпеки No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Виберіть @@ -7531,6 +7787,10 @@ chat item action Надіслати запит без повідомлення No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Надсилайте їх із галереї чи власних клавіатур. @@ -7541,6 +7801,10 @@ chat item action Надішліть до 100 останніх повідомлень новим користувачам. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Надсилайте свої приватні відгуки до груп. @@ -7556,6 +7820,10 @@ chat item action Можливо, відправник видалив запит на підключення. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Надсилання підтверджень доставки буде ввімкнено для всіх контактів у всіх видимих профілях чату. @@ -7815,6 +8083,14 @@ chat item action Налаштування були змінені. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Сформуйте зображення профілю @@ -7851,11 +8127,14 @@ chat item action Поділіться адресою публічно No comment provided by engineer. - - Share address with contacts? - Поділіться адресою з контактами? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Діліться з інших програм. @@ -7895,9 +8174,12 @@ chat item action Поділіться з SimpleX No comment provided by engineer. - - Share with contacts - Поділіться з контактами + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8147,6 +8429,11 @@ report reason Квадрат, коло або щось середнє між ними. No comment provided by engineer. + + Star on GitHub + Зірка на GitHub + No comment provided by engineer. + Start chat Почати чат @@ -8251,6 +8538,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8259,6 +8550,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8344,6 +8671,10 @@ Relay address was used to set up this relay for the channel. Сфотографуйте No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Натисніть Підключитися до чату @@ -8358,11 +8689,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8397,6 +8723,10 @@ Relay address was used to set up this relay for the channel. Натисніть, щоб приєднатися інкогніто No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Натисніть, щоб вставити посилання @@ -8498,6 +8828,10 @@ It can happen because of some bug or when the connection is compromised.Відсканований вами код не є QR-кодом посилання SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. З'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн. @@ -8523,9 +8857,9 @@ It can happen because of some bug or when the connection is compromised.Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з'єднання! No comment provided by engineer. - - The future of messaging - Наступне покоління приватних повідомлень + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8563,6 +8897,10 @@ It can happen because of some bug or when the connection is compromised.Стара база даних не була видалена під час міграції, її можна видалити. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Такі ж умови діятимуть і для оператора **%@**. @@ -8608,6 +8946,14 @@ It can happen because of some bug or when the connection is compromised.Теми No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Ці умови також поширюються на: **%@**. @@ -8730,6 +9076,10 @@ It can happen because of some bug or when the connection is compromised.Приховати небажані повідомлення. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Щоб створити нове з'єднання @@ -8816,11 +9166,6 @@ You will be prompted to complete authentication before this feature is enabled.< Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях. No comment provided by engineer. - - Toggle chat list: - Перемикання списку чату: - No comment provided by engineer. - Toggle incognito when connecting. Увімкніть інкогніто при підключенні. @@ -8836,6 +9181,10 @@ You will be prompted to complete authentication before this feature is enabled.< Непрозорість панелі інструментів No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total Всього @@ -9004,13 +9353,17 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link Несумісне посилання для підключення - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. Новим користувачам надсилається до 100 останніх повідомлень. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Оновлення @@ -9136,11 +9489,6 @@ To connect, please ask your contact to create another connection link and check Використовуйте TCP порт 443 лише для попередньо налаштованих серверів. No comment provided by engineer. - - Use chat - Використовуйте чат - No comment provided by engineer. - Use current profile Використовувати поточний профіль @@ -9224,6 +9572,10 @@ To connect, please ask your contact to create another connection link and check Використовуйте додаток однією рукою. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Використовувати веб-порт @@ -9375,6 +9727,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Чекаємо на десктопну версію... @@ -9415,6 +9771,10 @@ To connect, please ask your contact to create another connection link and check Попередження: ви можете втратити деякі дані! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Сервери WebRTC ICE @@ -9465,6 +9825,10 @@ To connect, please ask your contact to create another connection link and check Коли ви ділитеся з кимось своїм профілем інкогніто, цей профіль буде використовуватися для груп, до яких вас запрошують. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9724,6 +10088,12 @@ Repeat join request? Ви не можете надсилати повідомлення! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9733,11 +10103,6 @@ Repeat join request? Вас не вдалося верифікувати, спробуйте ще раз. No comment provided by engineer. - - You decide who can connect. - Ви вирішуєте, хто може під'єднатися. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9805,6 +10170,10 @@ Repeat connection request? Ви повинні отримувати сповіщення. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Ви зможете надсилати повідомлення **тільки після того, як ваш запит буде прийнято**. @@ -9943,6 +10312,10 @@ Repeat connection request? Ваші контакти залишаться на зв'язку. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Ваші облікові дані можуть бути надіслані незашифрованими. @@ -9963,6 +10336,10 @@ Repeat connection request? Ваша група No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Ваші уподобання @@ -10003,6 +10380,10 @@ Relays can access channel messages. Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам. alert message + + Your public address + No comment provided by engineer. + Your random profile Ваш випадковий профіль @@ -10031,21 +10412,11 @@ Relays can access channel messages. Ваші налаштування No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Внесок](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Напишіть нам електронною поштою](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Зірка на GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_курсив_ @@ -10200,6 +10571,10 @@ marked deleted chat item preview text дзвоніть… call status + + can't broadcast + No comment provided by engineer. + can't send messages не можна надсилати @@ -10854,6 +11229,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address видалено контактну адресу @@ -11173,6 +11552,10 @@ last received msg: %2$@ \~закреслити~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d981147cfe..51cbb94bda 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -185,9 +185,20 @@ %d 月 time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors 已跳过 %u 条消息。 No comment provided by engineer. + + (from owner) + chat link info line + (new) (新) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (此设备 v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - 以及更多! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval 一些杂项 No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact 新联系人 @@ -670,9 +715,8 @@ swipe action 活动连接 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. - 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action 已添加消息服务器 No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent 附加重音 @@ -860,6 +908,14 @@ swipe action 所有配置文件 profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. 将为你存档所有举报。 @@ -920,6 +976,10 @@ swipe action 仅有您的联系人许可后才允许不可撤回消息移除 No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. 只有您的联系人允许时才允许消息回应。 @@ -935,6 +995,10 @@ swipe action 允许向成员发送私信。 No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. 允许发送限时消息。 @@ -945,6 +1009,10 @@ swipe action 允许共享 No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) 允许不可撤回地删除已发送消息 @@ -1050,11 +1118,6 @@ swipe action 接听来电 No comment provided by engineer. - - Anybody can host servers. - 任何人都可以托管服务器。 - No comment provided by engineer. - App build: %@ 应用程序构建:%@ @@ -1260,6 +1323,21 @@ swipe action 错误消息散列 No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + 在你的网络中自由畅行。 + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + 因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。 + No comment provided by engineer. + Better calls 更佳的通话 @@ -1409,6 +1487,10 @@ swipe action 您和您的联系人都可以发送语音消息。 No comment provided by engineer. + + Bottom bar + No comment provided by engineer. + Broadcast compose placeholder for channel owner @@ -1421,7 +1503,7 @@ swipe action Business address 企业地址 - No comment provided by engineer. + chat link info line Business chats @@ -1443,15 +1525,6 @@ swipe action 通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。 No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - 使用 SimpleX Chat 代表您同意: -- 在公开群中只发送合法内容 -- 尊重其他用户 – 没有垃圾信息。 - No comment provided by engineer. - Call already ended! 通话已结束! @@ -1612,12 +1685,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1714,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1730,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat 聊天 @@ -1763,7 +1853,8 @@ set passcode view Chat with admins 和管理员聊天 - chat toolbar + chat feature +chat toolbar Chat with member @@ -1780,11 +1871,23 @@ set passcode view 聊天 No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members 和成员聊天 No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. 每 20 分钟检查消息。 @@ -1952,11 +2055,6 @@ set passcode view Configure relays No comment provided by engineer. - - Configure server operators - 配置服务器运营方 - No comment provided by engineer. - Confirm 确认 @@ -2062,6 +2160,10 @@ This is your own one-time link! 通过链接连接 new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link 通过一次性链接连接 @@ -2140,7 +2242,7 @@ This is your own one-time link! Connection error (AUTH) 连接错误(AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2197,6 +2299,10 @@ This is your own one-time link! 连接 No comment provided by engineer. + + Contact address + chat link info line + Contact allows 联系人允许 @@ -2267,6 +2373,11 @@ This is your own one-time link! 继续 No comment provided by engineer. + + Contribute + 贡献 + No comment provided by engineer. + Conversation deleted! 对话已删除! @@ -2297,11 +2408,6 @@ This is your own one-time link! 将名称更正为 %@? alert message - - Create - 创建 - No comment provided by engineer. - Create 1-time link 创建一次性链接 @@ -2370,11 +2476,19 @@ This is your own one-time link! 创建地址 No comment provided by engineer. + + Create your link + No comment provided by engineer. + Create your profile 创建您的资料 No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created 已创建 @@ -2557,11 +2671,6 @@ This is your own one-time link! 调试交付 No comment provided by engineer. - - Decentralized - 分散式 - No comment provided by engineer. - Decode link relay test step @@ -2959,6 +3068,14 @@ alert button 此群禁止成员间私信。 No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + + + Disable + alert button + Disable (keep overrides) 禁用(保留覆盖) @@ -3064,6 +3181,10 @@ alert button 不给新成员发送历史消息。 No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. 代理不使用身份验证凭据。 @@ -3165,6 +3286,10 @@ chat item action 端到端加密的通知。 No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit 编辑 @@ -3187,7 +3312,7 @@ chat item action Enable 启用 - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3223,6 +3348,10 @@ chat item action 启用相机访问 No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. 默认启用定时消失消息。 @@ -3243,16 +3372,15 @@ chat item action 启用即时通知? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock 启用锁定 No comment provided by engineer. - - Enable notifications - 启用通知 - No comment provided by engineer. - Enable periodic notifications? 启用定期通知? @@ -3387,6 +3515,10 @@ chat item action 在上面输入密码以显示! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -3419,7 +3551,7 @@ chat item action Error 错误 - No comment provided by engineer. + conn error description Error aborting address change @@ -3762,6 +3894,10 @@ chat item action 设置送达回执出错! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat 启动聊天错误 @@ -4130,6 +4266,10 @@ server test error 所有 moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: 为聊天资料 %@: @@ -4284,6 +4424,10 @@ Error: %2$@ 被提及时收到通知。 No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! 下午好! @@ -4342,7 +4486,7 @@ Error: %2$@ Group link 群组链接 - No comment provided by engineer. + chat link info line Group links @@ -4454,6 +4598,10 @@ Error: %2$@ 未发送历史消息给新成员。 No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works SimpleX的工作原理 @@ -4553,11 +4701,6 @@ Error: %2$@ 立即 No comment provided by engineer. - - Immune to spam - 不受垃圾和骚扰消息影响 - No comment provided by engineer. - Import 导入 @@ -4700,9 +4843,9 @@ More improvements are coming soon! 初始角色 No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - 安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + 安装用于终端的 SimpleX Chat No comment provided by engineer. @@ -4760,7 +4903,7 @@ More improvements are coming soon! Invalid connection link 无效的连接链接 - No comment provided by engineer. + conn error description Invalid display name! @@ -4825,6 +4968,10 @@ More improvements are coming soon! 邀请成员 No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat 邀请加入聊天 @@ -5025,6 +5172,10 @@ This is your link for group %@! 消耗更少的移动网络数据。 No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat 让我们一起在 SimpleX Chat 里聊天 @@ -5045,6 +5196,10 @@ This is your link for group %@! 连接移动端和桌面端应用程序!🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options 已链接桌面选项 @@ -5229,6 +5384,10 @@ This is your link for group %@! 群组成员可以添加信息回应。 No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) 群组成员可以不可撤回地删除已发送的消息 @@ -5393,6 +5552,14 @@ This is your link for group %@! 将显示来自 %@ 的消息! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. 此聊天中的消息永远不会被删除。 @@ -5423,16 +5590,15 @@ This is your link for group %@! 消息、文件和通话受到 **抗量子 e2e 加密** 的保护,具有完全正向保密、否认和闯入恢复。 No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device 迁移设备 No comment provided by engineer. - - Migrate from another device - 从另一台设备迁移 - No comment provided by engineer. - Migrate here 迁移到此处 @@ -5553,6 +5719,10 @@ This is your link for group %@! 网络和服务器 No comment provided by engineer. + + Network commitments + No comment provided by engineer. + Network connection 网络连接 @@ -5563,6 +5733,10 @@ This is your link for group %@! 网络去中心化 No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. 网络问题 - 消息在多次尝试发送后过期。 @@ -5578,6 +5752,11 @@ This is your link for group %@! 网络运营方 No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings 网络设置 @@ -5593,6 +5772,10 @@ This is your link for group %@! token status text + + New 1-time link + No comment provided by engineer. + New Passcode 新密码 @@ -5692,6 +5875,15 @@ This is your link for group %@! No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + + + No active relays + No comment provided by engineer. + No app password 没有应用程序密码 @@ -5850,9 +6042,18 @@ This is your link for group %@! 没有未读聊天 No comment provided by engineer. - - No user identifiers. - 没有用户标识符。 + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + 没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。 + No comment provided by engineer. + + + Non-profit governance + No comment provided by engineer. + + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + 别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。 No comment provided by engineer. @@ -5916,7 +6117,7 @@ This is your link for group %@! OK 好的 - No comment provided by engineer. + alert button Off @@ -5935,11 +6136,19 @@ new chat action 旧的数据库 No comment provided by engineer. + + On your phone, not on servers. + No comment provided by engineer. + One-time invitation link 一次性邀请链接 No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5959,6 +6168,10 @@ Requires compatible VPN. 将不会使用 Onion 主机。 No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. 仅聊天所有人可更改首选项。 @@ -6062,7 +6275,8 @@ Requires compatible VPN. Open 打开 - alert action + alert action +alert button Open Settings @@ -6098,6 +6312,10 @@ Requires compatible VPN. 打开条款 No comment provided by engineer. + + Open external link? + alert title + Open full link 打开完整链接 @@ -6167,6 +6385,13 @@ Requires compatible VPN. 运营方服务器 alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file 或者导入或者导入压缩文件 @@ -6187,6 +6412,10 @@ Requires compatible VPN. 或安全地分享此文件链接 No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code 或者显示此码 @@ -6197,6 +6426,10 @@ Requires compatible VPN. 或者私下分享 No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists 将聊天组织到列表 @@ -6222,6 +6455,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING 次数 @@ -6277,6 +6514,10 @@ Requires compatible VPN. 粘贴图片 No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! 粘贴链接以连接! @@ -6474,14 +6715,12 @@ Error: %@ 隐私政策和使用条款。 No comment provided by engineer. - - Privacy redefined - 重新定义隐私 + + Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - 服务器运营方无法访问私密聊天、群组和你的联系人。 + + Private and secure messaging. No comment provided by engineer. @@ -6553,9 +6792,8 @@ Error: %@ 个人资料主题 No comment provided by engineer. - - Profile update will be sent to your contacts. - 个人资料更新将被发送给您的联系人。 + + Profile update will be sent to your SimpleX contacts. alert message @@ -6563,6 +6801,10 @@ Error: %@ 禁止音频/视频通话。 No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. 禁止不可撤回消息删除。 @@ -6593,6 +6835,10 @@ Error: %@ 禁止向成员发送私信。 No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. 禁止发送限时消息。 @@ -6660,6 +6906,10 @@ Enable in *Network & servers* settings. 代理需要密码 No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications 推送通知 @@ -6700,24 +6950,14 @@ Enable in *Network & servers* settings. 阅读更多 No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - 阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。 + + Read more in User Guide. + 阅读更多User Guide。 No comment provided by engineer. - - 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) 中阅读更多内容。 - 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) 中阅读更多信息。 + + Read more in our GitHub repository. + 在我们的 GitHub 仓库 中阅读更多信息。 No comment provided by engineer. @@ -6892,6 +7132,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. 中继服务器仅在必要时使用。其他人可能会观察到您的IP地址。 @@ -6906,6 +7150,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove 移除 @@ -7188,6 +7436,10 @@ swipe action SOCKS代理 No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files 安全接收文件 @@ -7233,6 +7485,10 @@ chat item action 保存并通知群组成员 No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect 保存并重新连接 @@ -7431,6 +7687,10 @@ chat item action 安全码 No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select 选择 @@ -7561,6 +7821,10 @@ chat item action 发送无消息请求 No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. 发送它们来自图库或自定义键盘。 @@ -7571,6 +7835,10 @@ chat item action 给新成员发送最多 100 条历史消息。 No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. 向群发送私密反馈。 @@ -7586,6 +7854,10 @@ chat item action 发送人可能已删除连接请求。 No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. 将对所有可见聊天配置文件中的所有联系人启用送达回执功能。 @@ -7845,6 +8117,14 @@ chat item action 设置已修改。 alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images 改变个人资料图形状 @@ -7881,11 +8161,14 @@ chat item action 公开分享地址 No comment provided by engineer. - - Share address with contacts? - 与联系人分享地址? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. 从其他应用程序共享。 @@ -7925,9 +8208,12 @@ chat item action 分享到 SimpleX No comment provided by engineer. - - Share with contacts - 与联系人分享 + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8177,6 +8463,11 @@ report reason 方形、圆形、或两者之间的任意形状. No comment provided by engineer. + + Star on GitHub + 在 GitHub 上加星 + No comment provided by engineer. + Start chat 开始聊天 @@ -8280,6 +8571,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8288,6 +8583,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8373,6 +8704,10 @@ Relay address was used to set up this relay for the channel. 拍照 No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat 轻按连接进行聊天 @@ -8388,11 +8723,6 @@ Relay address was used to set up this relay for the channel. 轻按“连接”使用机器人 No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - 要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址” - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8427,6 +8757,10 @@ Relay address was used to set up this relay for the channel. 点击以加入隐身聊天 No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link 轻按粘贴链接 @@ -8528,6 +8862,10 @@ It can happen because of some bug or when the connection is compromised.您扫描的码不是 SimpleX 链接的二维码。 No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. 连接达到了未送达消息上限,你的联系人可能处于离线状态。 @@ -8553,9 +8891,9 @@ It can happen because of some bug or when the connection is compromised.加密正在运行,不需要新的加密协议。这可能会导致连接错误! No comment provided by engineer. - - The future of messaging - 下一代私密通讯软件 + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8593,6 +8931,11 @@ It can happen because of some bug or when the connection is compromised.旧数据库在迁移过程中没有被移除,可以删除。 No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + 人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。 + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8636,6 +8979,16 @@ It can happen because of some bug or when the connection is compromised.主题 No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + 然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。 + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + 还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。 + No comment provided by engineer. + These conditions will also apply for: **%@**. 这些条件将同样适用于: **%@**。 @@ -8759,6 +9112,10 @@ It can happen because of some bug or when the connection is compromised.隐藏不需要的信息。 No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection 建立新连接 @@ -8846,11 +9203,6 @@ You will be prompted to complete authentication before this feature is enabled.< 要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。 No comment provided by engineer. - - Toggle chat list: - 切换聊天列表: - No comment provided by engineer. - Toggle incognito when connecting. 在连接时切换隐身模式。 @@ -8865,6 +9217,10 @@ You will be prompted to complete authentication before this feature is enabled.< 工具栏不透明度 No comment provided by engineer. + + Top bar + No comment provided by engineer. + Total 共计 @@ -9034,13 +9390,17 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link 不支持的连接链接 - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. 给新成员发送了最多 100 条历史消息。 No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update 更新 @@ -9166,11 +9526,6 @@ To connect, please ask your contact to create another connection link and check 仅预设服务器使用 TCP 协议 443 端口。 No comment provided by engineer. - - Use chat - 使用聊天 - No comment provided by engineer. - Use current profile 使用当前配置文件 @@ -9254,6 +9609,10 @@ To connect, please ask your contact to create another connection link and check 用一只手使用应用程序。 No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port 使用 web 端口 @@ -9406,6 +9765,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... 正在等待桌面... @@ -9446,6 +9809,10 @@ To connect, please ask your contact to create another connection link and check 警告:您可能会丢失部分数据! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE 服务器 @@ -9496,6 +9863,10 @@ To connect, please ask your contact to create another connection link and check 当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。 No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9757,6 +10128,12 @@ Repeat join request? 您无法发送消息! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9766,11 +10143,6 @@ Repeat join request? 您的身份无法验证,请再试一次。 No comment provided by engineer. - - You decide who can connect. - 你决定谁可以连接。 - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9837,6 +10209,11 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + 你生来就没有账户。 + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. **只有在你的请求被接受后**你才能发送消息。 @@ -9973,6 +10350,11 @@ Repeat connection request? 与您的联系人保持连接。 No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + 你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。 + No comment provided by engineer. + Your credentials may be sent unencrypted. 你的凭据可能以未经加密的方式被发送。 @@ -9993,6 +10375,10 @@ Repeat connection request? 你的群 No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences 您的偏好设置 @@ -10033,6 +10419,10 @@ Relays can access channel messages. 您的个人资料已修改。如果进行保存,更新后的个人资料将发送到所有联系人。 alert message + + Your public address + No comment provided by engineer. + Your random profile 您的随机资料 @@ -10061,21 +10451,11 @@ Relays can access channel messages. 您的设置 No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [贡献](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [给我们发电邮](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_斜体_ @@ -10229,6 +10609,10 @@ marked deleted chat item preview text 呼叫中…… call status + + can't broadcast + No comment provided by engineer. + can't send messages 无法发送消息 @@ -10883,6 +11267,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address 删除了联系地址 @@ -11204,6 +11592,10 @@ last received msg: %2$@ \~删去~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index f13401d437..52c0405e5e 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -68,7 +68,7 @@ func apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, scope: chatInfo.groupChatScope(), - sendAsGroup: chatInfo.groupInfo.map { $0.useRelays && $0.membership.memberRole >= .owner } ?? false, + sendAsGroup: chatInfo.sendAsGroup, live: false, ttl: nil, composedMessages: composedMessages diff --git a/apps/ios/SimpleX SE/de.lproj/Localizable.strings b/apps/ios/SimpleX SE/de.lproj/Localizable.strings index 403fb3820a..df368686e8 100644 --- a/apps/ios/SimpleX SE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/de.lproj/Localizable.strings @@ -80,7 +80,7 @@ "Please create a profile in the SimpleX app" = "Bitte erstellen Sie in der SimpleX-App ein Profil"; /* No comment provided by engineer. */ -"Selected chat preferences prohibit this message." = "Die gewählten Chat-Einstellungen erlauben diese Nachricht nicht."; +"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt."; /* No comment provided by engineer. */ "Sending a message takes longer than expected." = "Das Senden einer Nachricht dauert länger als erwartet."; diff --git a/apps/ios/SimpleX SE/ru.lproj/Localizable.strings b/apps/ios/SimpleX SE/ru.lproj/Localizable.strings index 0841e8e47f..e4c8c000d4 100644 --- a/apps/ios/SimpleX SE/ru.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/ru.lproj/Localizable.strings @@ -29,7 +29,7 @@ "Database error" = "Ошибка базы данных"; /* No comment provided by engineer. */ -"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохраненного в keychain."; +"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохранённого в keychain."; /* No comment provided by engineer. */ "Database passphrase is required to open chat." = "Введите пароль базы данных, чтобы открыть чат."; @@ -77,7 +77,7 @@ "Passphrase" = "Пароль"; /* No comment provided by engineer. */ -"Please create a profile in the SimpleX app" = "Пожалуйста, создайте профиль в приложении SimpleX."; +"Please create a profile in the SimpleX app" = "Пожалуйста, создайте профиль в приложении SimpleX"; /* No comment provided by engineer. */ "Selected chat preferences prohibit this message." = "Выбранные настройки чата запрещают это сообщение."; diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index ac9bb3ff43..8c2e63bb59 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -182,8 +182,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -249,6 +249,7 @@ D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; E559A0A12E3F77EE00B26F74 /* CommandsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E559A0A02E3F77EE00B26F74 /* CommandsMenuView.swift */; }; + E5A0B0012F960000AAAA0001 /* YourNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A0B0022F960000AAAA0001 /* YourNetwork.swift */; }; E5AEC0AB2F91A6EB00270665 /* CIChatLinkHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */; }; E5AEC0AF2F91A73500270665 /* ComposeChatLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */; }; E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; }; @@ -558,8 +559,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -621,6 +622,7 @@ D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; E559A0A02E3F77EE00B26F74 /* CommandsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandsMenuView.swift; sourceTree = ""; }; + E5A0B0022F960000AAAA0001 /* YourNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourNetwork.swift; sourceTree = ""; }; E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatLinkHeader.swift; sourceTree = ""; }; E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeChatLinkView.swift; sourceTree = ""; }; E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SimpleXAssets.xcassets; sourceTree = ""; }; @@ -727,8 +729,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -814,8 +816,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a */, ); path = Libraries; sourceTree = ""; @@ -957,6 +959,7 @@ 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */, 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */, 640743602CD360E600158442 /* ChooseServerOperators.swift */, + E5A0B0022F960000AAAA0001 /* YourNetwork.swift */, E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */, ); path = Onboarding; @@ -1509,6 +1512,7 @@ 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */, 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */, 640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */, + E5A0B0012F960000AAAA0001 /* YourNetwork.swift in Sources */, 64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */, 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */, E5E418012F83D2CA00252B9E /* OnboardingCards.swift in Sources */, @@ -2065,7 +2069,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2090,7 +2094,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2115,7 +2119,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2140,7 +2144,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2157,11 +2161,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2177,11 +2181,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2202,7 +2206,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2217,7 +2221,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2239,7 +2243,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2254,7 +2258,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2276,7 +2280,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2302,7 +2306,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2327,7 +2331,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2354,7 +2358,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2381,7 +2385,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2396,7 +2400,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2415,7 +2419,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 331; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2430,7 +2434,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.5; + MARKETING_VERSION = 6.5.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 83b8d61ea1..1dfa477c91 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -867,6 +867,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case simplexLinks case reports case history + case support public var id: Self { self } @@ -888,10 +889,13 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: true case .reports: false case .history: false + case .support: false } } - public var text: String { + public var text: String { text(isChannel: false) } + + public func text(isChannel: Bool) -> String { switch self { case .timedMessages: return NSLocalizedString("Disappearing messages", comment: "chat feature") case .directMessages: return NSLocalizedString("Direct messages", comment: "chat feature") @@ -900,8 +904,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return NSLocalizedString("Voice messages", comment: "chat feature") case .files: return NSLocalizedString("Files and media", comment: "chat feature") case .simplexLinks: return NSLocalizedString("SimpleX links", comment: "chat feature") - case .reports: return NSLocalizedString("Member reports", comment: "chat feature") + case .reports: return isChannel + ? NSLocalizedString("Subscriber reports", comment: "chat feature") + : NSLocalizedString("Member reports", comment: "chat feature") case .history: return NSLocalizedString("Visible history", comment: "chat feature") + case .support: return NSLocalizedString("Chat with admins", comment: "chat feature") } } @@ -916,6 +923,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: return "link.circle" case .reports: return "flag" case .history: return "clock" + case .support: return "questionmark.circle" } } @@ -930,6 +938,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: return "link.circle.fill" case .reports: return "flag.fill" case .history: return "clock.fill" + case .support: return "questionmark.circle.fill" } } @@ -940,7 +949,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } } - public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool) -> LocalizedStringKey { + public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool, isChannel: Bool = false) -> LocalizedStringKey { if canEdit { switch self { case .timedMessages: @@ -950,8 +959,12 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } case .directMessages: switch enabled { - case .on: return "Allow sending direct messages to members." - case .off: return "Prohibit sending direct messages to members." + case .on: return isChannel + ? "Allow sending direct messages to subscribers." + : "Allow sending direct messages to members." + case .off: return isChannel + ? "Prohibit sending direct messages to subscribers." + : "Prohibit sending direct messages to members." } case .fullDelete: switch enabled { @@ -985,56 +998,96 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } case .history: switch enabled { - case .on: return "Send up to 100 last messages to new members." - case .off: return "Do not send history to new members." + case .on: return isChannel + ? "Send up to 100 last messages to new subscribers." + : "Send up to 100 last messages to new members." + case .off: return isChannel + ? "Do not send history to new subscribers." + : "Do not send history to new members." + } + case .support: + switch enabled { + case .on: return isChannel + ? "Allow subscribers to chat with admins." + : "Allow members to chat with admins." + case .off: return "Prohibit chats with admins." } } } else { switch self { case .timedMessages: switch enabled { - case .on: return "Members can send disappearing messages." + case .on: return isChannel + ? "Subscribers can send disappearing messages." + : "Members can send disappearing messages." case .off: return "Disappearing messages are prohibited." } case .directMessages: switch enabled { - case .on: return "Members can send direct messages." - case .off: return "Direct messages between members are prohibited." + case .on: return isChannel + ? "Subscribers can send direct messages." + : "Members can send direct messages." + case .off: return isChannel + ? "Direct messages between subscribers are prohibited." + : "Direct messages between members are prohibited." } case .fullDelete: switch enabled { - case .on: return "Members can irreversibly delete sent messages. (24 hours)" + case .on: return isChannel + ? "Subscribers can irreversibly delete sent messages. (24 hours)" + : "Members can irreversibly delete sent messages. (24 hours)" case .off: return "Irreversible message deletion is prohibited." } case .reactions: switch enabled { - case .on: return "Members can add message reactions." + case .on: return isChannel + ? "Subscribers can add message reactions." + : "Members can add message reactions." case .off: return "Message reactions are prohibited." } case .voice: switch enabled { - case .on: return "Members can send voice messages." + case .on: return isChannel + ? "Subscribers can send voice messages." + : "Members can send voice messages." case .off: return "Voice messages are prohibited." } case .files: switch enabled { - case .on: return "Members can send files and media." + case .on: return isChannel + ? "Subscribers can send files and media." + : "Members can send files and media." case .off: return "Files and media are prohibited." } case .simplexLinks: switch enabled { - case .on: return "Members can send SimpleX links." + case .on: return isChannel + ? "Subscribers can send SimpleX links." + : "Members can send SimpleX links." case .off: return "SimpleX links are prohibited." } case .reports: switch enabled { - case .on: return "Members can report messsages to moderators." + case .on: return isChannel + ? "Subscribers can report messsages to moderators." + : "Members can report messsages to moderators." case .off: return "Reporting messages to moderators is prohibited." } case .history: switch enabled { - case .on: return "Up to 100 last messages are sent to new members." - case .off: return "History is not sent to new members." + case .on: return isChannel + ? "Up to 100 last messages are sent to new subscribers." + : "Up to 100 last messages are sent to new members." + case .off: return isChannel + ? "History is not sent to new subscribers." + : "History is not sent to new members." + } + case .support: + switch enabled { + case .on: return isChannel + ? "Subscribers can chat with admins." + : "Members can chat with admins." + case .off: return "Chats with admins are prohibited." } } } @@ -1190,6 +1243,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { public var simplexLinks: RoleGroupPreference public var reports: GroupPreference public var history: GroupPreference + public var support: GroupPreference public var commands: [ChatBotCommand] public init( @@ -1202,6 +1256,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { simplexLinks: RoleGroupPreference, reports: GroupPreference, history: GroupPreference, + support: GroupPreference, commands: [ChatBotCommand] ) { self.timedMessages = timedMessages @@ -1213,6 +1268,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { self.simplexLinks = simplexLinks self.reports = reports self.history = history + self.support = support self.commands = commands } @@ -1226,6 +1282,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { simplexLinks: RoleGroupPreference(enable: .on, role: nil), reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on), + support: GroupPreference(enable: .on), commands: [] ) } @@ -1240,6 +1297,7 @@ public struct GroupPreferences: Codable, Hashable { public var simplexLinks: RoleGroupPreference? public var reports: GroupPreference? public var history: GroupPreference? + public var support: GroupPreference? public var commands: [ChatBotCommand]? public init( @@ -1252,6 +1310,7 @@ public struct GroupPreferences: Codable, Hashable { simplexLinks: RoleGroupPreference? = nil, reports: GroupPreference? = nil, history: GroupPreference? = nil, + support: GroupPreference? = nil, commands: [ChatBotCommand]? = nil ) { self.timedMessages = timedMessages @@ -1263,6 +1322,7 @@ public struct GroupPreferences: Codable, Hashable { self.simplexLinks = simplexLinks self.reports = reports self.history = history + self.support = support self.commands = commands } @@ -1276,6 +1336,7 @@ public struct GroupPreferences: Codable, Hashable { simplexLinks: RoleGroupPreference(enable: .on, role: nil), reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on), + support: GroupPreference(enable: .on), commands: nil ) } @@ -1760,6 +1821,18 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var sendAsGroup: Bool { + if let g = groupInfo, g.useRelays && g.membership.memberRole >= .owner { + switch groupChatScope() { + case .none: true + case .memberSupport: false + case .reports: false + } + } else { + false + } + } + public func ntfsEnabled(chatItem: ChatItem) -> Bool { ntfsEnabled(chatItem.meta.userMention) } @@ -4092,8 +4165,8 @@ public enum CIContent: Decodable, ItemContent, Hashable { case .rcvBlocked: return NSLocalizedString("blocked by admin", comment: "blocked chat item") case let .sndDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) case let .rcvDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) - case .sndGroupE2EEInfo: return e2eeInfoNoPQStr - case .rcvGroupE2EEInfo: return e2eeInfoNoPQStr + case let .sndGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo) + case let .rcvGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo) case .chatBanner: return "" case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item") } @@ -4105,6 +4178,12 @@ public enum CIContent: Decodable, ItemContent, Hashable { : e2eeInfoNoPQStr } + private func groupE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String { + e2eeInfo.public == true + ? NSLocalizedString("Messages in this channel are not end-to-end encrypted. Chat relays can see these messages.", comment: "E2EE info chat item") + : e2eeInfoNoPQStr + } + private var e2eeInfoNoPQStr: String { NSLocalizedString("This chat is protected by end-to-end encryption.", comment: "E2EE info chat item") } @@ -5319,6 +5398,7 @@ public enum CIGroupInvitationStatus: String, Decodable, Hashable { public struct E2EEInfo: Decodable, Hashable { public var pqEnabled: Bool? + public var `public`: Bool? } public enum RcvDirectEvent: Decodable, Hashable { diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index 451ac8b4ef..788ac12bae 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -25,6 +25,7 @@ extension ChatLike { case .files: p.files.on(for: groupInfo.membership) case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership) case .history: p.history.on + case .support: p.support.on case .reports: p.reports.on } } else { diff --git a/apps/ios/SimpleXChat/Theme/Color.swift b/apps/ios/SimpleXChat/Theme/Color.swift index 67626052a4..03685371ad 100644 --- a/apps/ios/SimpleXChat/Theme/Color.swift +++ b/apps/ios/SimpleXChat/Theme/Color.swift @@ -44,30 +44,53 @@ private func srgbToP3(red r: Double, green g: Double, blue b: Double, opacity a: return Color(.sRGB, red: r, green: g, blue: b, opacity: a) } -/// Create a Display P3 Color from oklch components. H in degrees. -func oklch(_ L: Double, _ C: Double, _ H: Double, alpha: Double = 1.0) -> Color { +// Create a Display P3 Color from oklch components. H in degrees +public func oklch(_ L: Double, _ C: Double, _ H: Double, alpha: Double = 1.0) -> Color { let hRad = H * .pi / 180.0 - let a = C * cos(hRad) - let b = C * sin(hRad) - // oklab → LMS (Ottosson 2021) - let l_ = L + 0.3963377774 * a + 0.2158037573 * b - let m_ = L - 0.1055613458 * a - 0.0638541728 * b - let s_ = L - 0.0894841775 * a - 1.2914855480 * b - let l = l_ * l_ * l_ - let m = m_ * m_ * m_ - let s = s_ * s_ * s_ - // LMS → linear Display P3 (direct, no sRGB clamping) - let lr = 3.1281105148 * l - 2.2570749853 * m + 0.1293047593 * s - let lg = -1.0911282009 * l + 2.4132668169 * m - 0.3221681599 * s - let lb = -0.0260136845 * l - 0.5080276339 * m + 1.5333166364 * s + let cosH = cos(hRad) + let sinH = sin(hRad) + + func linearP3(C: Double) -> (Double, Double, Double) { + let a = C * cosH + let b = C * sinH + // oklab → LMS (Ottosson 2021) + let l_ = L + 0.3963377774 * a + 0.2158037573 * b + let m_ = L - 0.1055613458 * a - 0.0638541728 * b + let s_ = L - 0.0894841775 * a - 1.2914855480 * b + let l = l_ * l_ * l_ + let m = m_ * m_ * m_ + let s = s_ * s_ * s_ + // LMS → linear Display P3 (direct, no sRGB clamping) + return ( + 3.1281105148 * l - 2.2570749853 * m + 0.1293047593 * s, + -1.0911282009 * l + 2.4132668169 * m - 0.3221681599 * s, + -0.0260136845 * l - 0.5080276339 * m + 1.5333166364 * s + ) + } + + func inGamut(_ r: Double, _ g: Double, _ b: Double) -> Bool { + r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1 + } + // linear P3 → gamma-encoded P3 (same transfer function as sRGB) - func gammaEncode(_ x: Double) -> Double { x >= 0.0031308 ? 1.055 * pow(x, 1.0 / 2.4) - 0.055 : 12.92 * x } - return Color(.displayP3, - red: gammaEncode(min(max(lr, 0), 1)), - green: gammaEncode(min(max(lg, 0), 1)), - blue: gammaEncode(min(max(lb, 0), 1)), - opacity: alpha - ) + func gammaEncode(_ x: Double) -> Double { + x >= 0.0031308 + ? 1.055 * pow(min(x, 1.0), 1.0 / 2.4) - 0.055 + : 12.92 * max(x, 0) + } + + var (r, g, b) = linearP3(C: C) + if !inGamut(r, g, b) { + var lo = 0.0, hi = C + while hi - lo > 1e-5 { + let mid = (lo + hi) / 2 + let (mr, mg, mb) = linearP3(C: mid) + if inGamut(mr, mg, mb) { lo = mid; r = mr; g = mg; b = mb } + else { hi = mid } + } + } + + return Color(.displayP3, red: gammaEncode(r), green: gammaEncode(g), blue: gammaEncode(b), opacity: alpha) } extension Color { @@ -155,7 +178,7 @@ extension Color { } } -extension String { +public extension String { func colorFromReadableHex() -> Color { // https://stackoverflow.com/a/56874327 let hex = self.trimmingCharacters(in: ["#", " "]) diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 323c5c6985..17e11d1020 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(това устройство v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Допринеси](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Изпратете ни имейл](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Звезда в GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Добави контакт**: за създаване на нов линк."; @@ -389,9 +383,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Активни връзки"; -/* 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 friends" = "Добави приятели"; @@ -635,9 +626,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Отговор на повикване"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; - /* No comment provided by engineer. */ "App build: %@" = "Компилация на приложението: %@"; @@ -873,7 +861,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Български, финландски, тайландски и украински - благодарение на потребителите и [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Бизнес адрес"; /* No comment provided by engineer. */ @@ -888,9 +876,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "С използването на SimpleX Chat вие се съгласявате със:\n- изпращане само на легално съдържание в публични групи.\n- уважение към другите потребители – без спам."; - /* No comment provided by engineer. */ "Call already ended!" = "Разговорът вече приключи!"; @@ -1068,7 +1053,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Чатът ще бъде изтрит за вас - това не може да бъде отменено!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Чат с администраторите"; /* No comment provided by engineer. */ @@ -1182,9 +1168,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Конфигурирай ICE сървъри"; -/* No comment provided by engineer. */ -"Configure server operators" = "Конфигуриране на сървърни оператори"; - /* No comment provided by engineer. */ "Confirm" = "Потвърди"; @@ -1318,7 +1301,7 @@ server test step */ /* alert title */ "Connection error" = "Грешка при свързване"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Грешка при свързване (AUTH)"; /* chat list item title (it should not be shown */ @@ -1384,6 +1367,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Продължи"; +/* No comment provided by engineer. */ +"Contribute" = "Допринеси"; + /* No comment provided by engineer. */ "Copy" = "Копирай"; @@ -1393,9 +1379,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Поправи име на %@?"; -/* No comment provided by engineer. */ -"Create" = "Създаване"; - /* No comment provided by engineer. */ "Create 1-time link" = "Създаване на еднократна препратка"; @@ -1522,9 +1505,6 @@ server test step */ /* time unit */ "days" = "дни"; -/* No comment provided by engineer. */ -"Decentralized" = "Децентрализиран"; - /* message decrypt error item */ "Decryption error" = "Грешка при декриптиране"; @@ -1802,7 +1782,7 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Редактирай групов профил"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Активирай"; /* No comment provided by engineer. */ @@ -1826,9 +1806,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "Активирай заключване"; -/* No comment provided by engineer. */ -"Enable notifications" = "Активирай известията"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Активирай периодични известия?"; @@ -1964,7 +1941,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "грешка"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Грешка при свързване със сървъра"; /* No comment provided by engineer. */ @@ -2306,7 +2283,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Груповата покана вече е невалидна, премахната е от подателя."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Групов линк"; /* No comment provided by engineer. */ @@ -2414,9 +2391,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "Веднага"; -/* No comment provided by engineer. */ -"Immune to spam" = "Защитен от спам и злоупотреби"; - /* No comment provided by engineer. */ "Import" = "Импортиране"; @@ -2502,7 +2476,7 @@ server test error */ "Initial role" = "Първоначална роля"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Инсталирайте SimpleX Chat за терминал"; /* No comment provided by engineer. */ "Instant" = "Мигновено"; @@ -2519,7 +2493,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "невалидни данни за чат"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Невалиден линк за връзка"; /* invalid chat item */ @@ -2825,9 +2799,6 @@ server test error */ /* No comment provided by engineer. */ "Migrate device" = "Мигрирай устройството"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Мигриране от друго устройство"; - /* No comment provided by engineer. */ "Migrate here" = "Мигрирай тук"; @@ -3002,9 +2973,6 @@ server test error */ /* copied message info in history */ "no text" = "няма текст"; -/* No comment provided by engineer. */ -"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; - /* No comment provided by engineer. */ "Not compatible!" = "Несъвместим!"; @@ -3040,7 +3008,7 @@ alert button new chat action */ "Ok" = "Ок"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "ОК"; /* No comment provided by engineer. */ @@ -3103,7 +3071,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Само вашият контакт може да изпраща гласови съобщения."; -/* alert action */ +/* alert action +alert button */ "Open" = "Отвори"; /* new chat action */ @@ -3250,9 +3219,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "Поверителност и сигурност"; -/* No comment provided by engineer. */ -"Privacy redefined" = "Поверителността преосмислена"; - /* No comment provided by engineer. */ "Private filenames" = "Поверителни имена на файлове"; @@ -3271,9 +3237,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "Профилна парола"; -/* alert message */ -"Profile update will be sent to your contacts." = "Актуализацията на профила ще бъде изпратена до вашите контакти."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Забрани аудио/видео разговорите."; @@ -3338,16 +3301,10 @@ new chat action */ "Read more" = "Прочетете още"; /* 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 our GitHub repository." = "Прочетете повече в нашето GitHub хранилище."; /* No comment provided by engineer. */ -"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)."; - -/* 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)."; +"Read more in User Guide." = "Прочетете повече в Ръководство за потребителя."; /* No comment provided by engineer. */ "Receipts are disabled" = "Потвърждениeто за доставка е деактивирано"; @@ -3782,18 +3739,12 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "Сподели адрес"; -/* alert title */ -"Share address with contacts?" = "Сподели адреса с контактите?"; - /* No comment provided by engineer. */ "Share link" = "Сподели линк"; /* No comment provided by engineer. */ "Share this 1-time invite link" = "Сподели този еднократен линк за връзка"; -/* No comment provided by engineer. */ -"Share with contacts" = "Сподели с контактите"; - /* No comment provided by engineer. */ "Show calls in phone history" = "Показване на обажданията в хронологията на телефона"; @@ -3881,6 +3832,9 @@ chat item action */ /* chat item text */ "standard end-to-end encryption" = "стандартно криптиране от край до край"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Звезда в GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Започни чат"; @@ -4020,9 +3974,6 @@ server test failure */ /* 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." = "Хешът на предишното съобщение е различен."; @@ -4251,9 +4202,6 @@ server test failure */ /* No comment provided by engineer. */ "Use .onion hosts" = "Използвай .onion хостове"; -/* No comment provided by engineer. */ -"Use chat" = "Използвай чата"; - /* new chat action */ "Use current profile" = "Използвай текущия профил"; @@ -4557,9 +4505,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Вече сте направили заявката за връзка!\nИзпрати отново заявката за свързване?"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 0031c2421b..f7e90e0c88 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(toto zařízení v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Přispějte](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Pošlete nám e-mail](mailto:chat@simplex.chat)"; -/* 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. */ "**Create 1-time link**: to create and share a new invitation link." = "**Vytvořit jednorázový odkaz**: pro vytvoření a sdílení nové pozvánky."; @@ -374,9 +368,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Aktivní spojení"; -/* 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 friends" = "Přidat přátele"; @@ -581,9 +572,6 @@ swipe action */ /* 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: %@"; @@ -683,6 +671,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Špatné ID zprávy"; +/* No comment provided by engineer. */ +"Be free in your network." = "Buďte svobodní ve své síti."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít."; + /* No comment provided by engineer. */ "Better calls" = "Lepší volání"; @@ -746,7 +740,7 @@ swipe action */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulharský, finský, thajský a ukrajinský - díky uživatelům a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Obchodní adresa"; /* No comment provided by engineer. */ @@ -1017,7 +1011,7 @@ server test step */ /* alert title */ "Connection error" = "Chyba připojení"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Chyba spojení (AUTH)"; /* chat list item title (it should not be shown */ @@ -1065,15 +1059,15 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Pokračovat"; +/* No comment provided by engineer. */ +"Contribute" = "Přispějte"; + /* No comment provided by engineer. */ "Copy" = "Kopírovat"; /* No comment provided by engineer. */ "Core version: v%@" = "Verze jádra: v%@"; -/* No comment provided by engineer. */ -"Create" = "Vytvořit"; - /* server test step */ "Create file" = "Vytvořit soubor"; @@ -1179,9 +1173,6 @@ server test step */ /* time unit */ "days" = "dní"; -/* No comment provided by engineer. */ -"Decentralized" = "Decentralizované"; - /* message decrypt error item */ "Decryption error" = "Chyba dešifrování"; @@ -1416,7 +1407,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "Upravit profil skupiny"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Zapnout"; /* No comment provided by engineer. */ @@ -1434,9 +1425,6 @@ alert button */ /* No comment provided by engineer. */ "Enable lock" = "Povolit zámek"; -/* No comment provided by engineer. */ -"Enable notifications" = "Povolit upozornění"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Povolit pravidelná oznámení?"; @@ -1548,7 +1536,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "chyba"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Chyba"; /* No comment provided by engineer. */ @@ -1824,7 +1812,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Skupinová pozvánka již není platná, byla odstraněna odesílatelem."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Odkaz na skupinu"; /* No comment provided by engineer. */ @@ -1926,9 +1914,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "Ihned"; -/* No comment provided by engineer. */ -"Immune to spam" = "Odolná vůči spamu a zneužití"; - /* No comment provided by engineer. */ "Import" = "Import"; @@ -1993,7 +1978,7 @@ server test error */ "Initial role" = "Počáteční role"; /* No comment provided by engineer. */ -"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)"; +"Install SimpleX Chat for terminal" = "Nainstalujte SimpleX Chat pro terminál"; /* No comment provided by engineer. */ "Instant" = "Okamžitě"; @@ -2010,7 +1995,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "neplatná chat data"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Neplatný odkaz na spojení"; /* invalid chat item */ @@ -2395,7 +2380,10 @@ server test error */ "no text" = "žádný text"; /* No comment provided by engineer. */ -"No user identifiers." = "Bez uživatelských identifikátorů."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén."; /* No comment provided by engineer. */ "Notifications" = "Oznámení"; @@ -2489,7 +2477,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Hlasové zprávy může odesílat pouze váš kontakt."; -/* alert action */ +/* alert action +alert button */ "Open" = "Otevřít"; /* new chat action */ @@ -2591,9 +2580,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "Ochrana osobních údajů a zabezpečení"; -/* No comment provided by engineer. */ -"Privacy redefined" = "Nové vymezení soukromí"; - /* No comment provided by engineer. */ "Private filenames" = "Soukromé názvy souborů"; @@ -2606,9 +2592,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "Heslo profilu"; -/* alert message */ -"Profile update will be sent to your contacts." = "Aktualizace profilu bude zaslána vašim kontaktům."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Zákaz audio/video hovorů."; @@ -2661,16 +2644,10 @@ new chat action */ "Read more" = "Přečíst více"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Více informací v [průvodci uživatele](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Přečtěte si více v našem GitHub repozitáři."; /* No comment provided by engineer. */ -"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)."; - -/* 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)."; +"Read more in User Guide." = "Více informací v průvodci uživatele."; /* No comment provided by engineer. */ "Receipts are disabled" = "Informace o dodání jsou zakázány"; @@ -3036,15 +3013,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "Sdílet adresu"; -/* alert title */ -"Share address with contacts?" = "Sdílet adresu s kontakty?"; - /* No comment provided by engineer. */ "Share link" = "Sdílet odkaz"; -/* No comment provided by engineer. */ -"Share with contacts" = "Sdílet s kontakty"; - /* No comment provided by engineer. */ "Show calls in phone history" = "Ukaž hovory v historii telefonu"; @@ -3114,6 +3085,9 @@ chat item action */ /* notification title */ "Somebody" = "Někdo"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Hvězda na GitHubu"; + /* No comment provided by engineer. */ "Start chat" = "Začít chat"; @@ -3232,9 +3206,6 @@ server test failure */ /* 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ší."; @@ -3250,6 +3221,9 @@ server test failure */ /* 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."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit."; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅"; @@ -3259,6 +3233,12 @@ server test failure */ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Servery pro nová připojení vašeho aktuálního profilu chatu **%@**."; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**."; @@ -3403,9 +3383,6 @@ server test failure */ /* No comment provided by engineer. */ "Use .onion hosts" = "Použít hostitele .onion"; -/* No comment provided by engineer. */ -"Use chat" = "Použijte chat"; - /* new chat action */ "Use current profile" = "Použít aktuální profil"; @@ -3578,7 +3555,7 @@ server test failure */ "You can set lock screen notification preview via settings." = "Náhled oznámení na zamykací obrazovce můžete změnit v nastavení."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte."; +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud odkaz později smažete."; /* 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 **%@**."; @@ -3610,9 +3587,6 @@ server test failure */ /* No comment provided by engineer. */ "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 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í."; @@ -3649,6 +3623,9 @@ server test failure */ /* chat list item description */ "you shared one-time link incognito" = "sdíleli jste jednorázový odkaz inkognito"; +/* No comment provided by engineer. */ +"You were born without an account" = "Narodili jste se bez účtu."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později!"; @@ -3700,6 +3677,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Vaše kontakty zůstanou připojeny."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Vaše aktuální chat databáze bude ODSTRANĚNA a NAHRAZENA importovanou."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 7137ccac8b..d5978b48dc 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -10,6 +10,9 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilere Zustellung von Nachrichten.\n- ein bisschen verbesserte Gruppen.\n- und mehr!"; +/* No comment provided by engineer. */ +"- opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking." = "- Opt‑in zum Senden von Linkvorschauen.\n- Hyperlink‑Phishing verhindern.\n- Link‑Tracking entfernen."; + /* No comment provided by engineer. */ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- Optionale Benachrichtigung von gelöschten Kontakten.\n- Profilnamen mit Leerzeichen.\n- Und mehr!"; @@ -19,21 +22,21 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 farbig!"; +/* chat link info line */ +"(from owner)" = "(vom Eigentümer)"; + /* No comment provided by engineer. */ "(new)" = "(Neu)"; +/* chat link info line */ +"(signed)" = "(signiert)"; + /* No comment provided by engineer. */ "(this device v%@)" = "(Dieses Gerät hat v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Unterstützen Sie uns](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Senden Sie uns eine E-Mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[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. */ "**Create 1-time link**: to create and share a new invitation link." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen."; @@ -64,6 +67,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Link scannen / einfügen**: Um eine Verbindung über den Link herzustellen, den Sie erhalten haben."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Relais testen** um seinen Namen abzurufen."; + /* 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."; @@ -175,6 +181,18 @@ /* time interval */ "%d months" = "%d Monate"; +/* channel relay bar +channel subscriber relay bar */ +"%d relays failed" = "%d Relais fehlgeschlagen"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays not active" = "%d Relais nicht aktiv"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays removed" = "%d Relais entfernt"; + /* time interval */ "%d sec" = "%d s"; @@ -184,15 +202,50 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d übersprungene Nachricht(en)"; +/* channel subscriber count */ +"%d subscriber" = "%d Abonnent"; + +/* channel subscriber count */ +"%d subscribers" = "%d Abonnenten"; + /* time interval */ "%d weeks" = "%d Wochen"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d Relais aktiv"; + +/* channel relay bar */ +"%d/%d relays active, %d errors" = "%1$d/%2$d Relais aktiv, %3$d Fehler"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d Relais aktiv, %3$d fehlgeschlagen"; + +/* channel relay bar */ +"%d/%d relays active, %d removed" = "%1$d/%2$d Relais aktiv, %3$d entfernt"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d Relais verbunden"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d Relais verbunden, %3$d Fehler"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d failed" = "%1$d/%2$d Relais verbunden, %3$d fehlgeschlagen"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d removed" = "%1$d/%2$d Relais verbunden, %3$d entfernt"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld Kanalereignisse"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld Kontakt(e) ausgewählt"; @@ -262,6 +315,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~durchstreichen~"; +/* owner verification */ +"⚠️ Signature verification failed: %@." = "⚠️ Signaturüberprüfung fehlgeschlagen: %@."; + /* time to disappear */ "0 sec" = "0 sek"; @@ -307,6 +363,9 @@ time interval */ /* No comment provided by engineer. */ "A few more things" = "Ein paar weitere Dinge"; +/* No comment provided by engineer. */ +"A link for one person to connect" = "Verbindungs-Link für eine Person"; + /* notification title */ "A new contact" = "Ein neuer Kontakt"; @@ -371,6 +430,9 @@ swipe action */ /* alert title */ "Accept member" = "Mitglied annehmen"; +/* No comment provided by engineer. */ +"accepted" = "Angenommen"; + /* rcv group event chat item */ "accepted %@" = "%@ angenommen"; @@ -392,6 +454,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Fehler bei der Bestätigung"; +/* No comment provided by engineer. */ +"active" = "Aktiv"; + /* token status text */ "Active" = "Aktiv"; @@ -399,7 +464,7 @@ swipe action */ "Active connections" = "Aktive Verbindungen"; /* 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."; +"Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre SimpleX-Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre SimpleX-Kontakte gesendet."; /* No comment provided by engineer. */ "Add friends" = "Freunde aufnehmen"; @@ -440,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "Added message servers" = "Nachrichtenserver hinzugefügt"; +/* No comment provided by engineer. */ +"Adding relays will be supported later." = "Das Hinzufügen von Relais wird zu einem späteren Zeitpunkt unterstützt."; + /* No comment provided by engineer. */ "Additional accent" = "Erste Akzentfarbe"; @@ -471,7 +539,7 @@ swipe action */ "Admins can block a member for all." = "Administratoren können ein Gruppenmitglied für Alle blockieren."; /* No comment provided by engineer. */ -"Admins can create the links to join groups." = "Administratoren können Links für den Beitritt zu Gruppen erzeugen."; +"Admins can create the links to join groups." = "Administratoren können Links für den Beitritt zu Gruppen erstellen."; /* No comment provided by engineer. */ "Advanced network settings" = "Erweiterte Netzwerkeinstellungen"; @@ -530,6 +598,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Alle Profile"; +/* No comment provided by engineer. */ +"All relays failed" = "Alle Relais fehlgeschlagen"; + +/* No comment provided by engineer. */ +"All relays removed" = "Alle Relais entfernt"; + /* No comment provided by engineer. */ "All reports will be archived for you." = "Alle Meldungen werden für Sie archiviert."; @@ -566,6 +640,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden)"; +/* No comment provided by engineer. */ +"Allow members to chat with admins." = "Mitgliedern den Chat mit Administratoren erlauben."; + /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Erlauben Sie Reaktionen auf Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt."; @@ -575,12 +652,18 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder erlauben."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Das Senden von Direktnachrichten an Abonnenten erlauben."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Das Senden von verschwindenden Nachrichten erlauben."; /* No comment provided by engineer. */ "Allow sharing" = "Teilen erlauben"; +/* No comment provided by engineer. */ +"Allow subscribers to chat with admins." = "Abonnenten den Chat mit Administratoren erlauben."; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden)"; @@ -650,9 +733,6 @@ swipe action */ /* 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: %@"; @@ -794,6 +874,15 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Falsche Nachrichten-ID"; +/* No comment provided by engineer. */ +"Be free\nin your network" = "Seien Sie frei\nin Ihrem Netzwerk"; + +/* No comment provided by engineer. */ +"Be free in your network." = "Genießen Sie die Freiheit in Ihrem Netzwerk."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Weil wir die Macht zerstört haben, zu wissen, wer Sie sind. Damit Ihnen Ihre Macht niemals genommen werden kann."; + /* No comment provided by engineer. */ "Better calls" = "Verbesserte Anrufe"; @@ -851,6 +940,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Mitglied blockieren?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Abonnent für alle blockieren?"; + /* marked deleted chat item preview text */ "blocked" = "Blockiert"; @@ -895,9 +987,15 @@ marked deleted chat item preview text */ "Both you and your contact can send voice messages." = "Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden."; /* No comment provided by engineer. */ -"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarisch, Finnisch, Thailändisch und Ukrainisch - Dank der Nutzer und [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +"Bottom bar" = "Untere Leiste"; + +/* compose placeholder for channel owner */ +"Broadcast" = "Broadcast"; /* No comment provided by engineer. */ +"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarisch, Finnisch, Thailändisch und Ukrainisch - Dank der Nutzer und [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* chat link info line */ "Business address" = "Geschäftliche Adresse"; /* No comment provided by engineer. */ @@ -912,9 +1010,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam."; - /* No comment provided by engineer. */ "call" = "Anrufen"; @@ -939,6 +1034,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Camera not available" = "Kamera nicht verfügbar"; +/* No comment provided by engineer. */ +"can't broadcast" = "Broadcast nicht möglich"; + /* No comment provided by engineer. */ "Can't call contact" = "Kontakt kann nicht angerufen werden"; @@ -1038,6 +1136,58 @@ set passcode view */ /* chat item text */ "changing address…" = "Wechsel der Empfängeradresse wurde gestartet…"; +/* shown as sender role for channel messages */ +"channel" = "Kanal"; + +/* No comment provided by engineer. */ +"Channel" = "Kanal"; + +/* No comment provided by engineer. */ +"Channel display name" = "Anzeigename des Kanals"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Vollständiger Kanalname (optional)"; + +/* alert message +alert subtitle */ +"Channel has no active relays. Please try to join later." = "Der Kanal hat keine aktiven Relais. Bitte später erneut versuchen."; + +/* No comment provided by engineer. */ +"Channel image" = "Kanalbild"; + +/* chat link info line */ +"Channel link" = "Kanallink"; + +/* No comment provided by engineer. */ +"Channel preferences" = "Kanal-Präferenzen"; + +/* No comment provided by engineer. */ +"Channel profile" = "Kanalprofil"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "Das Kanalprofil wird auf den Geräten der Abonnenten und auf den Chat‑Relais gespeichert."; + +/* snd group event chat item */ +"channel profile updated" = "Kanalprofil wurde aktualisiert"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "Das Kanalprofil wurde geändert. Beim Speichern wird das aktualisierte Profil an die Abonnenten des Kanals gesendet."; + +/* alert title */ +"Channel temporarily unavailable" = "Der Kanal ist vorübergehend nicht erreichbar"; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "Der Kanal wird für alle Abonnenten gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "Der Kanal wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "Der Kanal wird mit %1$d von %2$d Relais gestartet. Fortfahren?"; + +/* No comment provided by engineer. */ +"Channels" = "Kanäle"; + /* No comment provided by engineer. */ "Chat" = "Chat"; @@ -1089,6 +1239,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Benutzerprofil"; +/* No comment provided by engineer. */ +"Chat relay" = "Chat-Relais"; + +/* No comment provided by engineer. */ +"Chat relays" = "Chat-Relais"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "Chat‑Relais leiten Nachrichten in den von Ihnen erstellten Kanälen weiter."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "Chat‑Relais leiten Nachrichten an Kanal-Abonnenten weiter."; + /* No comment provided by engineer. */ "Chat theme" = "Chat-Design"; @@ -1098,7 +1260,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chat mit Administratoren"; /* No comment provided by engineer. */ @@ -1110,15 +1273,30 @@ set passcode view */ /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Chats with admins are prohibited." = "Chats mit Administratoren sind nicht erlaubt."; + +/* alert message */ +"Chats with admins in public channels have no E2E encryption - use only with trusted chat relays." = "Chats mit Administratoren in öffentlichen Kanälen sind nicht Ende‑zu‑Ende‑verschlüsselt – bitte nur über vertrauenswürdige Chat‑Relais nutzen."; + /* No comment provided by engineer. */ "Chats with members" = "Chats mit Mitgliedern"; +/* No comment provided by engineer. */ +"Chats with members are disabled" = "Chats mit Mitgliedern sind deaktiviert"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Alle 20min Nachrichten überprüfen."; /* No comment provided by engineer. */ "Check messages when allowed." = "Wenn es erlaubt ist, Nachrichten überprüfen."; +/* alert message */ +"Check relay address and try again." = "Relais-Adresse überprüfen und erneut versuchen."; + +/* alert message */ +"Check relay name and try again." = "Relais-Name überprüfen und erneut versuchen."; + /* alert title */ "Check server address and try again." = "Überprüfen Sie die Serveradresse und versuchen Sie es nochmal."; @@ -1213,7 +1391,7 @@ set passcode view */ "Configure ICE servers" = "ICE-Server konfigurieren"; /* No comment provided by engineer. */ -"Configure server operators" = "Server-Betreiber konfigurieren"; +"Configure relays" = "Relais konfigurieren"; /* No comment provided by engineer. */ "Confirm" = "Bestätigen"; @@ -1279,6 +1457,9 @@ server test step */ /* new chat sheet title */ "Connect via link" = "Über einen Link verbinden"; +/* No comment provided by engineer. */ +"Connect via link or QR code" = "Über einen Link oder QR-Code verbinden"; + /* new chat sheet title */ "Connect via one-time link" = "Über einen Einmal-Link verbinden"; @@ -1301,7 +1482,7 @@ server test step */ "Connected to desktop" = "Mit dem Desktop verbunden"; /* No comment provided by engineer. */ -"connecting" = "verbinde"; +"connecting" = "Verbinde"; /* No comment provided by engineer. */ "Connecting" = "Verbinden"; @@ -1316,7 +1497,7 @@ server test step */ "connecting (introduced)" = "Verbindung (erstellt)"; /* No comment provided by engineer. */ -"connecting (introduction invitation)" = "Verbinde (nach einer Einladung)"; +"connecting (introduction invitation)" = "Verbindung (nach einer Einladung)"; /* call status */ "connecting call" = "Anruf wird verbunden…"; @@ -1348,7 +1529,7 @@ server test step */ /* alert title */ "Connection error" = "Verbindungsfehler"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Verbindungsfehler (AUTH)"; /* chat list item title (it should not be shown */ @@ -1393,6 +1574,9 @@ server test step */ /* profile update event chat item */ "contact %@ changed to %@" = "Der Kontaktname wurde von %1$@ auf %2$@ geändert"; +/* chat link info line */ +"Contact address" = "Kontaktadresse"; + /* No comment provided by engineer. */ "Contact allows" = "Der Kontakt erlaubt"; @@ -1453,6 +1637,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Weiter"; +/* No comment provided by engineer. */ +"Contribute" = "Unterstützen Sie uns"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Chat-Inhalte entfernt!"; @@ -1471,14 +1658,11 @@ server test step */ /* alert message */ "Correct name to %@?" = "Richtiger Name für %@?"; -/* No comment provided by engineer. */ -"Create" = "Erstellen"; - /* No comment provided by engineer. */ "Create 1-time link" = "Einmal-Link erstellen"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil."; +"Create a group using a random profile." = "Gruppe mit einem zufälligen Profil erstellen."; /* server test step */ "Create file" = "Datei erstellen"; @@ -1490,7 +1674,7 @@ server test step */ "Create group link" = "Gruppenlink erstellen"; /* No comment provided by engineer. */ -"Create link" = "Link erzeugen"; +"Create link" = "Link erstellen"; /* No comment provided by engineer. */ "Create list" = "Liste erstellen"; @@ -1501,8 +1685,14 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Profil erstellen"; +/* No comment provided by engineer. */ +"Create public channel" = "Öffentlichen Kanal erstellen"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Öffentlichen Kanal erstellen (BETA)"; + /* server test step */ -"Create queue" = "Erzeuge Warteschlange"; +"Create queue" = "Warteschlange erstellen"; /* No comment provided by engineer. */ "Create SimpleX address" = "SimpleX-Adresse erstellen"; @@ -1511,7 +1701,13 @@ server test step */ "Create your address" = "Ihre Adresse erstellen"; /* No comment provided by engineer. */ -"Create your profile" = "Erstellen Sie Ihr Profil"; +"Create your link" = "Ihren Link erstellen"; + +/* No comment provided by engineer. */ +"Create your profile" = "Ihr Profil erstellen"; + +/* No comment provided by engineer. */ +"Create your public address" = "Ihre öffentliche Adresse erstellen"; /* No comment provided by engineer. */ "Created" = "Erstellt"; @@ -1525,6 +1721,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Archiv-Link erzeugen"; +/* No comment provided by engineer. */ +"Creating channel" = "Kanal wird erstellt"; + /* No comment provided by engineer. */ "Creating link…" = "Link wird erstellt…"; @@ -1627,8 +1826,8 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Debugging-Zustellung"; -/* No comment provided by engineer. */ -"Decentralized" = "Dezentral"; +/* relay test step */ +"Decode link" = "Link dekodieren"; /* message decrypt error item */ "Decryption error" = "Entschlüsselungsfehler"; @@ -1671,6 +1870,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Kontakt löschen und benachrichtigen"; +/* No comment provided by engineer. */ +"Delete channel" = "Kanal löschen"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Kanal löschen?"; + /* No comment provided by engineer. */ "Delete chat" = "Chat löschen"; @@ -1774,6 +1979,9 @@ alert button */ /* server test step */ "Delete queue" = "Lösche Warteschlange"; +/* No comment provided by engineer. */ +"Delete relay" = "Relais löschen"; + /* No comment provided by engineer. */ "Delete report" = "Meldung löschen"; @@ -1798,6 +2006,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Gelöscht um: %@"; +/* rcv group event chat item */ +"deleted channel" = "Kanal gelöscht"; + /* rcv direct event chat item */ "deleted contact" = "Gelöschter Kontakt"; @@ -1888,6 +2099,12 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "Direktnachrichten zwischen Abonnenten sind nicht erlaubt."; + +/* alert button */ +"Disable" = "Deaktivieren"; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Deaktivieren (vorgenommene Einstellungen bleiben erhalten)"; @@ -1904,7 +2121,7 @@ alert button */ "Disable SimpleX Lock" = "SimpleX-Sperre deaktivieren"; /* No comment provided by engineer. */ -"disabled" = "deaktiviert"; +"disabled" = "Deaktiviert"; /* No comment provided by engineer. */ "Disabled" = "Deaktiviert"; @@ -1945,6 +2162,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Den Nachrichtenverlauf nicht an neue Mitglieder senden."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Den Nachrichtenverlauf nicht an neue Abonnenten senden."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Nachrichten werden nicht direkt versendet, selbst wenn Ihr oder der Zielserver kein privates Routing unterstützt."; @@ -2024,27 +2244,39 @@ chat item action */ /* No comment provided by engineer. */ "E2E encrypted notifications." = "E2E-verschlüsselte Benachrichtigungen."; +/* No comment provided by engineer. */ +"Easier to invite your friends 👋" = "Freunde einladen – jetzt noch einfacher 👋"; + /* chat item action */ "Edit" = "Bearbeiten"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Kanalprofil bearbeiten"; + /* No comment provided by engineer. */ "Edit group profile" = "Gruppenprofil bearbeiten"; /* No comment provided by engineer. */ "Empty message!" = "Leere Nachricht!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Aktivieren"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Aktivieren (vorgenommene Einstellungen bleiben erhalten)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Aktivieren Sie mindestens ein Chat‑Relais unter 'Netzwerk & Server'."; + /* alert title */ "Enable automatic message deletion?" = "Automatisches Löschen von Nachrichten aktivieren?"; /* No comment provided by engineer. */ "Enable camera access" = "Kamera-Zugriff aktivieren"; +/* alert title */ +"Enable chats with admins?" = "Chats mit Administratoren aktivieren?"; + /* No comment provided by engineer. */ "Enable disappearing messages by default." = "Verschwindende Nachrichten sind per Voreinstellung aktiviert."; @@ -2060,11 +2292,11 @@ chat item action */ /* No comment provided by engineer. */ "Enable instant notifications?" = "Sofortige Benachrichtigungen aktivieren?"; -/* No comment provided by engineer. */ -"Enable lock" = "Sperre aktivieren"; +/* alert title */ +"Enable link previews?" = "Linkvorschau aktivieren?"; /* No comment provided by engineer. */ -"Enable notifications" = "Benachrichtigungen aktivieren"; +"Enable lock" = "Sperre aktivieren"; /* No comment provided by engineer. */ "Enable periodic notifications?" = "Periodische Benachrichtigungen aktivieren?"; @@ -2171,6 +2403,9 @@ chat item action */ /* call status */ "ended call %@" = "Anruf beendet %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Kanalname eingeben…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Geben Sie das korrekte Passwort ein."; @@ -2189,6 +2424,12 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Für die Anzeige das Passwort im Suchfeld eingeben!"; +/* No comment provided by engineer. */ +"Enter profile name..." = "Profilname eingeben..."; + +/* No comment provided by engineer. */ +"Enter relay name…" = "Relais-Name eingeben…"; + /* No comment provided by engineer. */ "Enter server manually" = "Geben Sie den Server manuell ein"; @@ -2207,7 +2448,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "Fehler"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Fehler"; /* No comment provided by engineer. */ @@ -2225,6 +2466,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* alert title */ +"Error adding relay" = "Fehler beim Hinzufügen des Relais"; + /* alert title */ "Error adding server" = "Fehler beim Hinzufügen des Servers"; @@ -2261,6 +2505,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Fehler beim Erstellen der Adresse"; +/* alert title */ +"Error creating channel" = "Fehler beim Erstellen des Kanals"; + /* No comment provided by engineer. */ "Error creating group" = "Fehler beim Erzeugen der Gruppe"; @@ -2366,6 +2613,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Fehler beim Zurücksetzen der Statistiken"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Fehler beim Speichern des Kanalprofils"; + /* alert title */ "Error saving chat list" = "Fehler beim Speichern der Chat-Liste"; @@ -2408,6 +2658,9 @@ chat item action */ /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Fehler beim Setzen von Empfangsbestätigungen!"; +/* alert title */ +"Error sharing channel" = "Fehler beim Teilen des Kanals"; + /* No comment provided by engineer. */ "Error starting chat" = "Fehler beim Starten des Chats"; @@ -2450,6 +2703,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Fehler: "; +/* receive error chat item */ +"error: %@" = "Fehler: %@"; + /* alert message file error text snd error text */ @@ -2634,6 +2890,9 @@ server test error */ /* No comment provided by engineer. */ "For all moderators" = "Für alle Moderatoren"; +/* No comment provided by engineer. */ +"For anyone to reach you" = "Damit Sie jeder erreichen kann"; + /* servers error servers warning */ "For chat profile %@:" = "Für das Chat-Profil %@:"; @@ -2719,9 +2978,15 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Weiter reduzierter Batterieverbrauch"; +/* relay test step */ +"Get link" = "Link erhalten"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Bei Erwähnung benachrichtigt werden."; +/* No comment provided by engineer. */ +"Get started" = "Jetzt starten"; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIFs und Sticker"; @@ -2767,7 +3032,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "Gruppe wurde gelöscht"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Gruppen-Link"; /* No comment provided by engineer. */ @@ -2839,6 +3104,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "Der Nachrichtenverlauf wird nicht an neue Gruppenmitglieder gesendet."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "Der Nachrichtenverlauf wird nicht an neue Abonnenten gesendet."; + /* time unit */ "hours" = "Stunden"; @@ -2899,9 +3167,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Sofort"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immun gegen Spam und Missbrauch"; - /* No comment provided by engineer. */ "Import" = "Importieren"; @@ -3002,7 +3267,7 @@ servers warning */ "Initial role" = "Anfängliche Rolle"; /* No comment provided by engineer. */ -"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)"; +"Install SimpleX Chat for terminal" = "Installieren Sie SimpleX Chat als Terminalanwendung"; /* No comment provided by engineer. */ "Instant" = "Sofort"; @@ -3037,7 +3302,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "Ungültige Chat-Daten"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Ungültiger Verbindungslink"; /* invalid chat item */ @@ -3058,6 +3323,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Ungültiger QR-Code"; +/* alert title */ +"Invalid relay address!" = "Ungültige Relais-Adresse!"; + +/* alert title */ +"Invalid relay name!" = "Ungültiger Relais-Name!"; + /* No comment provided by engineer. */ "Invalid response" = "Ungültige Reaktion"; @@ -3085,6 +3356,9 @@ servers warning */ /* No comment provided by engineer. */ "Invite members" = "Mitglieder einladen"; +/* No comment provided by engineer. */ +"Invite someone privately" = "Für privaten Chat einladen"; + /* No comment provided by engineer. */ "Invite to chat" = "Zum Chat einladen"; @@ -3151,6 +3425,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "Als %@ beitreten"; +/* No comment provided by engineer. */ +"Join channel" = "Kanal beitreten"; + /* new chat sheet title */ "Join group" = "Treten Sie der Gruppe bei"; @@ -3199,6 +3476,12 @@ servers warning */ /* swipe action */ "Leave" = "Verlassen"; +/* No comment provided by engineer. */ +"Leave channel" = "Kanal verlassen"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Kanal verlassen?"; + /* No comment provided by engineer. */ "Leave chat" = "Chat verlassen"; @@ -3217,6 +3500,9 @@ servers warning */ /* No comment provided by engineer. */ "Less traffic on mobile networks." = "Weniger Datenverkehr in mobilen Netzen."; +/* No comment provided by engineer. */ +"Let someone connect to you" = "Jemand mit Ihnen verbinden lassen"; + /* email subject */ "Let's talk in SimpleX Chat" = "Lassen Sie uns in SimpleX Chat kommunizieren"; @@ -3226,9 +3512,15 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Einschränkungen"; +/* No comment provided by engineer. */ +"link" = "Link"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗"; +/* owner verification */ +"Link signature verified." = "Linksignatur erfolgreich überprüft."; + /* No comment provided by engineer. */ "Linked desktop options" = "Verknüpfte Desktop-Optionen"; @@ -3320,7 +3612,7 @@ servers warning */ "Member admission" = "Aufnahme von Mitgliedern"; /* rcv group event chat item */ -"member connected" = "ist der Gruppe beigetreten"; +"member connected" = "Verbunden"; /* No comment provided by engineer. */ "member has old version" = "Das Mitglied hat eine alte App-Version"; @@ -3358,6 +3650,9 @@ servers warning */ /* No comment provided by engineer. */ "Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; +/* No comment provided by engineer. */ +"Members can chat with admins." = "Mitglieder können mit Administratoren chatten."; + /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; @@ -3400,6 +3695,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Nachrichtenentwurf"; +/* No comment provided by engineer. */ +"Message error" = "Übertragungsfehler"; + /* item status text */ "Message forwarded" = "Nachricht weitergeleitet"; @@ -3460,6 +3758,12 @@ servers warning */ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!"; +/* No comment provided by engineer. */ +"Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages." = "Nachrichten in diesem Kanal sind **nicht Ende‑zu‑Ende‑verschlüsselt**. Chat‑Relais können diese Nachrichten sehen."; + +/* E2EE info chat item */ +"Messages in this channel are not end-to-end encrypted. Chat relays can see these messages." = "Nachrichten in diesem Kanal sind nicht Ende‑zu‑Ende‑verschlüsselt. Chat‑Relais können diese Nachrichten sehen."; + /* alert message */ "Messages in this chat will never be deleted." = "Nachrichten in diesem Chat werden nie gelöscht."; @@ -3479,10 +3783,10 @@ servers warning */ "Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt."; /* No comment provided by engineer. */ -"Migrate device" = "Gerät migrieren"; +"Migrate" = "Migrieren"; /* No comment provided by engineer. */ -"Migrate from another device" = "Von einem anderen Gerät migrieren"; +"Migrate device" = "Gerät migrieren"; /* No comment provided by engineer. */ "Migrate here" = "Hierher migrieren"; @@ -3574,12 +3878,18 @@ servers warning */ /* No comment provided by engineer. */ "Network & servers" = "Netzwerk & Server"; +/* No comment provided by engineer. */ +"Network commitments" = "Netzwerk Verpflichtungen"; + /* No comment provided by engineer. */ "Network connection" = "Netzwerkverbindung"; /* No comment provided by engineer. */ "Network decentralization" = "Dezentralisiertes Netzwerk"; +/* conn error description */ +"Network error" = "Netzwerk-Fehler"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen."; @@ -3589,6 +3899,9 @@ servers warning */ /* No comment provided by engineer. */ "Network operator" = "Netzwerk-Betreiber"; +/* No comment provided by engineer. */ +"Network routers cannot know\nwho talks to whom" = "Netzwerk‑Router können nicht erkennen,\nwer mit wem kommuniziert"; + /* No comment provided by engineer. */ "Network settings" = "Netzwerkeinstellungen"; @@ -3598,15 +3911,24 @@ servers warning */ /* delete after time */ "never" = "nie"; +/* No comment provided by engineer. */ +"new" = "Neu"; + /* token status text */ "New" = "Neu"; +/* No comment provided by engineer. */ +"New 1-time link" = "Neuer Einmal-Link"; + /* No comment provided by engineer. */ "New chat" = "Neuer Chat"; /* No comment provided by engineer. */ "New chat experience 🎉" = "Neue Chat-Erfahrung 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Neues Chat-Relais"; + /* notification */ "New contact request" = "Neue Kontaktanfrage"; @@ -3664,9 +3986,21 @@ servers warning */ /* No comment provided by engineer. */ "No" = "Nein"; +/* No comment provided by engineer. */ +"No account. No phone. No email. No ID.\nThe most secure encryption." = "Kein Account. Keine Telefonnummer. Keine E‑Mail. Keine ID.\nDie sicherste Verschlüsselung."; + +/* No comment provided by engineer. */ +"No active relays" = "Keine aktiven Relais"; + /* Authentication unavailable */ "No app password" = "Kein App-Passwort"; +/* No comment provided by engineer. */ +"No chat relays" = "Keine Chat-Relais"; + +/* servers warning */ +"No chat relays enabled." = "Es sind keine Chat-Relais aktiviert."; + /* No comment provided by engineer. */ "No chats" = "Keine Chats"; @@ -3764,7 +4098,16 @@ servers warning */ "No unread chats" = "Keine ungelesenen Chats"; /* No comment provided by engineer. */ -"No user identifiers." = "Keine Benutzerkennungen."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Niemand verfolgte Ihre Gespräche. Niemand erstellte eine Karte, wo Sie sich aufgehalten haben. Privatsphäre war nie ein Feature - sie war selbstverständlich."; + +/* No comment provided by engineer. */ +"Non-profit governance" = "Non‑Profit‑Governance"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nicht ein besseres Schloss an der Tür eines Anderen. Kein freundlicher Vermieter, der Ihre Privatsphäre respektiert, aber dennoch jeden Besucher registriert. Sie sind kein Gast. Sie sind zu Hause. Kein Vermieter, kein Fremder kann es betreten - Sie sind souverän."; + +/* alert title */ +"Not all relays connected" = "Es sind nicht alle Relais verbunden"; /* No comment provided by engineer. */ "Not compatible!" = "Nicht kompatibel!"; @@ -3822,7 +4165,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3831,9 +4174,15 @@ new chat action */ /* group pref value */ "on" = "Ein"; +/* No comment provided by engineer. */ +"On your phone, not on servers." = "Auf Ihrem Gerät, nicht auf Servern."; + /* No comment provided by engineer. */ "One-time invitation link" = "Einmal-Einladungslink"; +/* chat link info line */ +"One-time link" = "Einmal-Link"; + /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Für diese Verbindung werden Onion-Hosts benötigt.\nDies erfordert die Aktivierung eines VPNs."; @@ -3844,7 +4193,10 @@ new chat action */ "Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet."; /* No comment provided by engineer. */ -"Only chat owners can change preferences." = "Nur Chat-Eigentümer können die Präferenzen ändern."; +"Only channel owners can change channel preferences." = "Kanal-Präferenzen können nur von Kanal-Eigentümern geändert werden."; + +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Präferenzen können nur von Chat-Eigentümern geändert werden."; /* No comment provided by engineer. */ "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."; @@ -3903,12 +4255,16 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden."; -/* alert action */ +/* alert action +alert button */ "Open" = "Öffnen"; /* No comment provided by engineer. */ "Open changes" = "Änderungen öffnen"; +/* new chat action */ +"Open channel" = "Kanal öffnen"; + /* new chat action */ "Open chat" = "Chat öffnen"; @@ -3921,6 +4277,9 @@ new chat action */ /* No comment provided by engineer. */ "Open conditions" = "Nutzungsbedingungen öffnen"; +/* alert title */ +"Open external link?" = "Externen Link öffnen?"; + /* alert action */ "Open full link" = "Vollständigen Link öffnen"; @@ -3933,6 +4292,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; +/* new chat action */ +"Open new channel" = "Neuen Kanal öffnen"; + /* new chat action */ "Open new chat" = "Neuen Chat öffnen"; @@ -3963,6 +4325,9 @@ new chat action */ /* alert title */ "Operator server" = "Betreiber-Server"; +/* No comment provided by engineer. */ +"Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code" = "Betreiber verpflichten sich:\n- Unabhängig zu bleiben\n- Metadaten auf ein Minimum zu reduzieren\n- Geprüften Open‑Source‑Code einzusetzen"; + /* No comment provided by engineer. */ "Or import archive file" = "Oder importieren Sie eine Archiv-Datei"; @@ -3975,12 +4340,18 @@ new chat action */ /* No comment provided by engineer. */ "Or securely share this file link" = "Oder teilen Sie diesen Datei-Link sicher"; +/* No comment provided by engineer. */ +"Or show QR in person or via video call." = "Oder den QR‑Code persönlich oder per Videoanruf zeigen."; + /* No comment provided by engineer. */ "Or show this code" = "Oder diesen QR-Code anzeigen"; /* No comment provided by engineer. */ "Or to share privately" = "Oder zum privaten Teilen"; +/* No comment provided by engineer. */ +"Or use this QR - print or show online." = "Oder diesen QR‑Code verwenden – ausgedruckt oder online."; + /* No comment provided by engineer. */ "Organize chats into lists" = "Chats in Listen verwalten"; @@ -3999,9 +4370,18 @@ new chat action */ /* member role */ "owner" = "Eigentümer"; +/* No comment provided by engineer. */ +"Owner" = "Eigentümer"; + /* feature role */ "owners" = "Eigentümer"; +/* No comment provided by engineer. */ +"Owners" = "Eigentümer"; + +/* No comment provided by engineer. */ +"Ownership: you can run your own relays." = "Volle Kontrolle: Sie können Ihre eigenen Relais betreiben."; + /* No comment provided by engineer. */ "Passcode" = "Zugangscode"; @@ -4029,6 +4409,9 @@ new chat action */ /* No comment provided by engineer. */ "Paste image" = "Bild einfügen"; +/* No comment provided by engineer. */ +"Paste link / Scan" = "Link einfügen / Scannen"; + /* No comment provided by engineer. */ "Paste link to connect!" = "Zum Verbinden den Link einfügen!"; @@ -4137,6 +4520,12 @@ new chat action */ /* 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 relay address" = "Voreingestellte Relais-Adresse"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Voreingestellter Relais-Name"; + /* No comment provided by engineer. */ "Preset server address" = "Voreingestellte Serveradresse"; @@ -4159,10 +4548,10 @@ new chat action */ "Privacy policy and conditions of use." = "Datenschutz- und Nutzungsbedingungen."; /* No comment provided by engineer. */ -"Privacy redefined" = "Datenschutz neu definiert"; +"Privacy: for owners and subscribers." = "Privatsphäre: für Besitzer und Abonnenten."; /* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich."; +"Private and secure messaging." = "Private und sichere Kommunikation."; /* No comment provided by engineer. */ "Private filenames" = "Neutrale Dateinamen"; @@ -4188,6 +4577,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Zeitüberschreitung der privaten Routing-Sitzung"; +/* alert action */ +"Proceed" = "Fortfahren"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil und Serververbindungen"; @@ -4204,11 +4596,14 @@ new chat action */ "Profile theme" = "Profil-Design"; /* alert message */ -"Profile update will be sent to your contacts." = "Profil-Aktualisierung wird an Ihre Kontakte gesendet."; +"Profile update will be sent to your SimpleX contacts." = "Profil-Aktualisierung wird an Ihre SimpleX-Kontakte gesendet."; /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Audio-/Video-Anrufe nicht erlauben."; +/* No comment provided by engineer. */ +"Prohibit chats with admins." = "Chat mit Administratoren nicht erlauben."; + /* No comment provided by engineer. */ "Prohibit irreversible message deletion." = "Unwiederbringliches löschen von Nachrichten nicht erlauben."; @@ -4224,6 +4619,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "Das Senden von Direktnachrichten an Abonnenten nicht erlauben."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Das Senden von verschwindenden Nachrichten nicht erlauben."; @@ -4266,6 +4664,9 @@ new chat action */ /* No comment provided by engineer. */ "Proxy requires password" = "Der Proxy benötigt ein Passwort"; +/* No comment provided by engineer. */ +"Public channels - speak freely 🚀" = "Öffentliche Kanäle – frei sprechen 🚀"; + /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; @@ -4294,16 +4695,10 @@ new chat action */ "Read more" = "Mehr erfahren"; /* 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 our GitHub repository." = "Erfahren Sie in unserem GitHub-Repository mehr dazu."; /* No comment provided by engineer. */ -"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."; - -/* 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."; +"Read more in User Guide." = "Lesen Sie mehr dazu im Benutzerhandbuch."; /* No comment provided by engineer. */ "Receipts are disabled" = "Bestätigungen sind deaktiviert"; @@ -4412,12 +4807,36 @@ swipe action */ /* call status */ "rejected call" = "Abgelehnter Anruf"; +/* member role */ +"relay" = "Relais"; + +/* No comment provided by engineer. */ +"Relay" = "Relais"; + +/* alert title */ +"Relay address" = "Relais-Adresse"; + +/* alert title */ +"Relay connection failed" = "Relais-Verbindung fehlgeschlagen"; + +/* No comment provided by engineer. */ +"Relay link" = "Relais-Link"; + +/* alert message */ +"Relay results:" = "Relay‑Status:"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "Relais-Server schützen Ihre IP-Adresse, aber sie können die Anrufdauer erfassen."; +/* No comment provided by engineer. */ +"Relay test failed!" = "Relais-Test fehlgeschlagen!"; + +/* No comment provided by engineer. */ +"Reliability: many relays per channel." = "Zuverlässigkeit: mehrere Relais pro Kanal."; + /* alert action */ "Remove" = "Entfernen"; @@ -4442,12 +4861,24 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Passwort aus dem Schlüsselbund entfernen?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Abonnent entfernen"; + +/* alert title */ +"Remove subscriber?" = "Abonnent entfernen?"; + /* No comment provided by engineer. */ "removed" = "entfernt"; +/* receive error chat item */ +"removed (%d attempts)" = "Entfernt (%d Versuche)"; + /* rcv group event chat item */ "removed %@" = "hat %@ aus der Gruppe entfernt"; +/* No comment provided by engineer. */ +"removed by operator" = "Vom Betreiber entfernt"; + /* profile update event chat item */ "removed contact address" = "Die Kontaktadresse wurde entfernt"; @@ -4616,6 +5047,9 @@ swipe action */ /* No comment provided by engineer. */ "Run chat" = "Chat starten"; +/* No comment provided by engineer. */ +"Safe web links" = "Sichere Web-Links"; + /* No comment provided by engineer. */ "Safely receive files" = "Dateien sicher herunterladen"; @@ -4632,6 +5066,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Speichern (und Mitglieder benachrichtigen)"; +/* alert button */ +"Save (and notify subscribers)" = "Speichern (Abonnenten benachrichtigen)"; + /* alert title */ "Save admission settings?" = "Speichern der Aufnahme-Einstellungen?"; @@ -4641,12 +5078,21 @@ chat item action */ /* No comment provided by engineer. */ "Save and notify group members" = "Speichern und Gruppenmitglieder benachrichtigen"; +/* No comment provided by engineer. */ +"Save and notify subscribers" = "Speichern und Abonnenten benachrichtigen"; + /* No comment provided by engineer. */ "Save and reconnect" = "Speichern und neu verbinden"; /* No comment provided by engineer. */ "Save and update group profile" = "Gruppen-Profil sichern und aktualisieren"; +/* No comment provided by engineer. */ +"Save channel profile" = "Kanalprofil speichern"; + +/* alert title */ +"Save channel profile?" = "Kanalprofil speichern?"; + /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; @@ -4776,6 +5222,9 @@ chat item action */ /* chat item text */ "security code changed" = "Sicherheitscode wurde geändert"; +/* No comment provided by engineer. */ +"Security: owners hold channel keys." = "Sicherheit: Eigentümer besitzen die Kanalschlüssel."; + /* chat item action */ "Select" = "Auswählen"; @@ -4786,7 +5235,7 @@ chat item action */ "Selected %lld" = "%lld ausgewählt"; /* No comment provided by engineer. */ -"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt."; +"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt."; /* No comment provided by engineer. */ "Self-destruct" = "Selbstzerstörung"; @@ -4822,7 +5271,7 @@ chat item action */ "Send errors" = "Fehler beim Senden"; /* No comment provided by engineer. */ -"Send link previews" = "Link-Vorschau senden"; +"Send link previews" = "Linkvorschau senden"; /* No comment provided by engineer. */ "Send live message" = "Live Nachricht senden"; @@ -4854,12 +5303,18 @@ chat item action */ /* No comment provided by engineer. */ "Send request without message" = "Anfrage ohne Nachricht senden"; +/* No comment provided by engineer. */ +"Send the link via any messenger - it's secure. Ask to paste into SimpleX." = "Den Link über einen beliebigen Messenger versenden – es ist sicher. Bitte in SimpleX einfügen."; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Bis zu 100 der letzten Nachrichten an neue Abonnenten senden."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Senden Sie Ihr privates Feedback an Gruppen."; @@ -4869,6 +5324,9 @@ chat item action */ /* No comment provided by engineer. */ "Sender may have deleted the connection request." = "Der Absender hat möglicherweise die Verbindungsanfrage gelöscht."; +/* alert message */ +"Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later." = "Das Senden einer Link-Vorschau kann Ihre IP‑Adresse an die Website übermitteln. Sie können dies später in den Datenschutzeinstellungen ändern."; + /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Das Senden von Empfangsbestätigungen an alle Kontakte in allen sichtbaren Chat-Profilen wird aktiviert."; @@ -4947,6 +5405,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "Server-Warteschlangen-Information: %1$@\n\nZuletzt empfangene Nachricht: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "Der Server erfordert eine Autorisierung, um eine Verbindung zum Relais herzustellen. Bitte Passwort überprüfen."; + /* server test error */ "Server requires authorization to create queues, check password." = "Der Server erfordert zum Erstellen von Warteschlangen eine Autorisierung. Bitte überprüfen Sie das Passwort."; @@ -5031,6 +5492,12 @@ chat item action */ /* alert message */ "Settings were changed." = "Die Einstellungen wurden geändert."; +/* No comment provided by engineer. */ +"Setup notifications" = "Benachrichtigungen einrichten"; + +/* No comment provided by engineer. */ +"Setup routers" = "Router einrichten"; + /* No comment provided by engineer. */ "Shape profile images" = "Form der Profil-Bilder"; @@ -5051,7 +5518,10 @@ chat item action */ "Share address publicly" = "Die Adresse öffentlich teilen"; /* alert title */ -"Share address with contacts?" = "Die Adresse mit Kontakten teilen?"; +"Share address with SimpleX contacts?" = "Die Adresse mit SimpleX-Kontakten teilen?"; + +/* No comment provided by engineer. */ +"Share channel" = "Kanal teilen"; /* No comment provided by engineer. */ "Share from other apps." = "Aus anderen Apps heraus teilen."; @@ -5068,6 +5538,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Profil teilen"; +/* No comment provided by engineer. */ +"Share relay address" = "Relais-Adresse teilen"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Die SimpleX-Adresse auf sozialen Medien teilen."; @@ -5078,7 +5551,10 @@ chat item action */ "Share to SimpleX" = "Mit SimpleX teilen"; /* No comment provided by engineer. */ -"Share with contacts" = "Mit Kontakten teilen"; +"Share via chat" = "Per Chat teilen"; + +/* No comment provided by engineer. */ +"Share with SimpleX contacts" = "Mit SimpleX-Kontakten teilen"; /* No comment provided by engineer. */ "Share your address" = "Ihre Adresse teilen"; @@ -5138,7 +5614,7 @@ chat item action */ "SimpleX address settings" = "Einstellungen automatisch akzeptieren"; /* simplex link type */ -"SimpleX channel link" = "SimpleX-Kanal-Link"; +"SimpleX channel link" = "SimpleX-Kanallink"; /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX-Chat und Flux haben vereinbart, die von Flux betriebenen Server in die App aufzunehmen."; @@ -5182,6 +5658,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Die SimpleX-Protokolle wurden von Trail of Bits überprüft."; +/* simplex link type */ +"SimpleX relay address" = "SimpleX Relais-Adresse"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereinfachter Inkognito-Modus"; @@ -5234,6 +5713,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "Standard-Ende-zu-Ende-Verschlüsselung"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Stern auf GitHub vergeben"; + /* No comment provided by engineer. */ "Start chat" = "Starten Sie den Chat"; @@ -5300,6 +5782,48 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Abonniert"; +/* No comment provided by engineer. */ +"Subscriber" = "Abonnent"; + +/* chat feature */ +"Subscriber reports" = "Abonnenten-Meldungen"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "Abonnent wird aus dem Kanal entfernt. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Abonnenten"; + +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Abonnenten können eine Reaktion auf Nachrichten geben."; + +/* No comment provided by engineer. */ +"Subscribers can chat with admins." = "Abonnenten können mit Administratoren chatten."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Abonnenten können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Abonnenten können Nachrichten an Moderatoren melden."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Abonnenten können Direktnachrichten versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Abonnenten können verschwindende Nachrichten versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Abonnenten können Dateien und Medien versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Abonnenten können SimpleX-Links versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Abonnenten können Sprachnachrichten versenden."; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Abonnenten verbinden sich über den Relais‑Link mit dem Kanal.\nDie Relais-Adresse wurde zur Einrichtung dieses Relais für diesen Kanal verwendet."; + /* No comment provided by engineer. */ "Subscription errors" = "Fehler beim Abonnieren"; @@ -5327,6 +5851,9 @@ report reason */ /* No comment provided by engineer. */ "Take picture" = "Machen Sie ein Foto"; +/* No comment provided by engineer. */ +"Talk to someone" = "Mit jemandem sprechen"; + /* No comment provided by engineer. */ "Tap button " = "Schaltfläche antippen "; @@ -5340,13 +5867,13 @@ report reason */ "Tap Connect to use bot" = "Verbinden tippen, um den Bot zu nutzen."; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen."; +"Tap Join channel" = "Tippen, um dem Kanal beizutreten"; /* No comment provided by engineer. */ "Tap Join group" = "Tippen, um der Gruppe beizutreten"; /* No comment provided by engineer. */ -"Tap to activate profile." = "Zum Aktivieren des Profils tippen."; +"Tap to activate profile." = "Tippen, um das Profil zu aktivieren."; /* No comment provided by engineer. */ "Tap to Connect" = "Zum Verbinden tippen"; @@ -5358,7 +5885,10 @@ report reason */ "Tap to join incognito" = "Zum Inkognito beitreten tippen"; /* No comment provided by engineer. */ -"Tap to paste link" = "Zum Link einfügen tippen"; +"Tap to open" = "Zum Öffnen tippen"; + +/* No comment provided by engineer. */ +"Tap to paste link" = "Tippen, um den Link einzufügen"; /* No comment provided by engineer. */ "Tap to scan" = "Zum Scannen tippen"; @@ -5394,6 +5924,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Benachrichtigungen testen"; +/* No comment provided by engineer. */ +"Test relay" = "Relais testen"; + /* No comment provided by engineer. */ "Test server" = "Teste Server"; @@ -5421,6 +5954,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Durch Verwendung verschiedener Netzwerk-Betreiber für jede Unterhaltung schützt die App Ihre Privatsphäre."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "Die App hat diese Nachricht nach %lld Empfangsversuchen entfernt."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Die App wird eine Bestätigung bei Downloads von unbekannten Datei-Servern anfordern (außer bei .onion)."; @@ -5430,6 +5966,9 @@ server test failure */ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Der von Ihnen gescannte Code ist kein SimpleX-Link-QR-Code."; +/* conn error description */ +"The connection reached the limit of undelivered messages" = "Die Verbindung hat das Limit für nicht zugestellte Nachrichten erreicht"; + /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline."; @@ -5446,7 +5985,7 @@ server test failure */ "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"; +"The first network where you own\nyour contacts and groups." = "Das erste Netzwerk,\nin dem Sie Ihre Kontakte und Gruppen besitzen."; /* No comment provided by engineer. */ "The hash of the previous message is different." = "Der Hash der vorherigen Nachricht unterscheidet sich."; @@ -5472,6 +6011,9 @@ server test failure */ /* 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."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Die älteste Freiheit des Menschen - mit einem anderen Menschen sprechen zu können, ohne beobachtet zu werden - gestützt auf einer Infrastruktur, die Sie nicht verraten kann."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; @@ -5499,6 +6041,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Design"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Dann sind wir online gegangen, und jede Plattform wollte Etwas von Ihnen - Ihren Namen, Ihre Nummer, Ihre Freunde. Wir akzeptierten, dass es der Preis mit Anderen zu kommunizieren ist, Jemandem preiszugeben, mit wem und wie wir miteinander kommunizieren. Jede Generation, Menschen und Technologien, kannten es nur so - Telefon, E-Mail, Messenger, soziale Medien. Es schien der einzig mögliche Weg zu sein."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Es gibt einen anderen Weg. Ein Netzwerk ohne Telefonnummern, ohne Benutzernamen, ohne Benutzerkennungen und ohne jegliche Benutzeridentität. Ein Netzwerk, welches Menschen verbindet und verschlüsselte Nachrichten überträgt, ohne zu wissen, wer mit wem verbunden ist."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Diese Nutzungsbedingungen gelten auch für: **%@**."; @@ -5541,6 +6089,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Diese Gruppe existiert nicht mehr."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Dies ist eine Chat‑Relais-Adresse, welche nicht zum Verbinden verwendet werden kann."; + +/* new chat action */ +"This is your link for channel %@!" = "Dies ist Ihr Link für den Kanal %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden."; @@ -5574,6 +6128,9 @@ server test failure */ /* 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 make SimpleX Network last." = "Für ein dauerhaftes SimpleX-Netzwerk."; + /* No comment provided by engineer. */ "To protect against your link being replaced, you can compare contact security codes." = "Zum Schutz vor dem Austausch Ihres Links können Sie die Sicherheitscodes Ihrer Kontakte vergleichen."; @@ -5622,9 +6179,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Chat-Liste umschalten:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Inkognito beim Verbinden einschalten."; @@ -5634,6 +6188,9 @@ server test failure */ /* No comment provided by engineer. */ "Toolbar opacity" = "Deckkraft der Symbolleiste"; +/* No comment provided by engineer. */ +"Top bar" = "Obere Leiste"; + /* No comment provided by engineer. */ "Total" = "Summe aller Abonnements"; @@ -5673,6 +6230,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Mitglied freigeben?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Abonnent für alle freigeben?"; + /* rcv group event chat item */ "unblocked %@" = "hat %@ freigegeben"; @@ -5745,12 +6305,15 @@ server test failure */ /* swipe action */ "Unread" = "Ungelesen"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Verbindungs-Link wird nicht unterstützt"; /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Bis zu 100 der letzten Nachrichten werden an neue Abonnenten gesendet."; + /* No comment provided by engineer. */ "Update" = "Aktualisieren"; @@ -5763,6 +6326,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Einstellungen aktualisieren?"; +/* rcv group event chat item */ +"updated channel profile" = "Kanalprofil aktualisiert"; + /* No comment provided by engineer. */ "Updated conditions" = "Aktualisierte Nutzungsbedingungen"; @@ -5820,9 +6386,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Verwende %@"; -/* No comment provided by engineer. */ -"Use chat" = "Verwenden Sie Chat"; - /* new chat action */ "Use current profile" = "Aktuelles Profil nutzen"; @@ -5832,6 +6395,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Für Nachrichten verwenden"; +/* No comment provided by engineer. */ +"Use for new channels" = "Für neue Kanäle verwenden"; + /* No comment provided by engineer. */ "Use for new connections" = "Für neue Verbindungen nutzen"; @@ -5856,6 +6422,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Sie nutzen privates Routing mit unbekannten Servern."; +/* No comment provided by engineer. */ +"Use relay" = "Relais verwenden"; + /* No comment provided by engineer. */ "Use server" = "Server nutzen"; @@ -5880,6 +6449,9 @@ server test failure */ /* No comment provided by engineer. */ "Use the app with one hand." = "Die App mit einer Hand bedienen."; +/* No comment provided by engineer. */ +"Use this address in your social media profile, website, or email signature." = "Diese Adresse in Ihrem Social‑Media‑Profil, auf Ihrer Webseite oder in Ihrer E‑Mail‑Signatur verwenden."; + /* No comment provided by engineer. */ "Use web port" = "Web-Port nutzen"; @@ -5898,6 +6470,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Überprüfen"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Code mit dem Desktop überprüfen"; @@ -5919,6 +6494,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Sicherheitscode überprüfen"; +/* relay hostname */ +"via %@" = "via %@"; + /* No comment provided by engineer. */ "Via browser" = "Über den Browser"; @@ -5988,9 +6566,18 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "Sprachnachrichten sind nicht erlaubt!"; +/* alert action */ +"Wait" = "Abwarten"; + +/* relay test step */ +"Wait response" = "Antwort abwarten"; + /* No comment provided by engineer. */ "waiting for answer…" = "Warten auf Antwort…"; +/* No comment provided by engineer. */ +"Waiting for channel owner to add relays." = "Warte auf das Hinzufügen von Relais durch den Eigentümer des Kanals."; + /* No comment provided by engineer. */ "waiting for confirmation…" = "Warten auf Bestätigung…"; @@ -6021,6 +6608,9 @@ server test failure */ /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Warnung: Sie könnten einige Daten verlieren!"; +/* No comment provided by engineer. */ +"We made connecting simpler for new users." = "Wir haben das Verbinden für neue Nutzer vereinfacht."; + /* No comment provided by engineer. */ "WebRTC ICE servers" = "WebRTC ICE-Server"; @@ -6057,6 +6647,9 @@ server test failure */ /* 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."; +/* No comment provided by engineer. */ +"Why SimpleX is built." = "Warum SimpleX entwickelt wurde."; + /* No comment provided by engineer. */ "WiFi" = "WiFi"; @@ -6156,6 +6749,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "Sie sind Beobachter"; +/* No comment provided by engineer. */ +"you are subscriber" = "Sie sind Abonnent"; + /* snd group event chat item */ "you blocked %@" = "Sie haben %@ blockiert"; @@ -6198,6 +6794,9 @@ server test failure */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Über die Geräte-Einstellungen können Sie die Benachrichtigungsvorschau im Sperrbildschirm erlauben."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the channel." = "Sie können einen Link oder QR-Code teilen - damit kann jeder dem Kanal beitreten."; + /* No comment provided by engineer. */ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Sie können diesen Link oder QR-Code teilen - Damit kann jede Person der Gruppe beitreten. Wenn Sie den Link später löschen, werden Sie keine Gruppenmitglieder verlieren, die der Gruppe darüber beigetreten sind."; @@ -6238,10 +6837,13 @@ server test failure */ "you changed role of %@ to %@" = "Sie haben die Rolle von %1$@ auf %2$@ geändert"; /* 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 commit to:\n- Only legal content in public groups\n- Respect other users - no spam" = "Sie verpflichten sich dazu:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden\n- andere Nutzer zu respektieren - kein Spam"; /* No comment provided by engineer. */ -"You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; +"You connected to the channel via this relay link." = "Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden."; + +/* No comment provided by engineer. */ +"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut."; /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Sie haben bereits ein Verbindungsanfrage beantragt!\nVerbindungsanfrage wiederholen?"; @@ -6297,6 +6899,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "Sie haben %@ freigegeben"; +/* No comment provided by engineer. */ +"You were born without an account" = "Sie wurden ohne eine Benutzerkennung geboren."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Sie können erst dann Nachrichten versenden, **sobald Ihre Anfrage angenommen wurde**."; @@ -6318,6 +6923,9 @@ server test failure */ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Sie werden keine Nachrichten mehr aus diesem Kanal erhalten. Der Chatverlauf bleibt erhalten."; + /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf bleibt erhalten."; @@ -6342,6 +6950,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Anrufe"; +/* No comment provided by engineer. */ +"Your channel" = "Ihr Kanal"; + /* No comment provided by engineer. */ "Your chat database" = "Chat-Datenbank"; @@ -6372,6 +6983,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ihre Kontakte bleiben weiterhin verbunden."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Ihre Kommunikation gehört Ihnen, so wie es immer war, bevor es das Internet gab. Das Netzwerk ist kein Ort, den Sie besuchen. Es ist ein Ort, den Sie erschaffen und besitzen und Niemand kann es Ihnen nehmen, egal ob Sie es privat oder öffentlich machen."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Ihre Anmeldeinformationen können unverschlüsselt versendet werden."; @@ -6387,6 +7001,9 @@ server test failure */ /* No comment provided by engineer. */ "Your ICE servers" = "Ihre ICE-Server"; +/* No comment provided by engineer. */ +"Your network" = "Ihr Netzwerk"; + /* No comment provided by engineer. */ "Your preferences" = "Ihre Präferenzen"; @@ -6396,6 +7013,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Mein Profil"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "Ihr Profil **%@** wird mit Kanal‑Relais und Abonnenten geteilt.\nRelais können auf Kanalnachrichten zugreifen."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt."; @@ -6408,9 +7028,18 @@ server test failure */ /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet."; +/* No comment provided by engineer. */ +"Your public address" = "Ihre öffentliche Adresse"; + /* No comment provided by engineer. */ "Your random profile" = "Ihr Zufallsprofil"; +/* No comment provided by engineer. */ +"Your relay address" = "Ihre Relais-Adresse"; + +/* No comment provided by engineer. */ +"Your relay name" = "Ihr Relais-Name"; + /* No comment provided by engineer. */ "Your server address" = "Ihre Serveradresse"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 42ed52c412..49826ff7f6 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -10,6 +10,9 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- entrega de mensajes más estable.\n- grupos un poco mejores.\n- ¡y más!"; +/* No comment provided by engineer. */ +"- opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking." = "- aceptar el envío de vistas previas de los enlaces.\n- prevenir el phishing mediante hipervínculos.\n- eliminar el seguimiento de los enlaces."; + /* No comment provided by engineer. */ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- notificar opcionalmente a los contactos eliminados.\n- nombres de perfil con espacios.\n- ¡...y más!"; @@ -19,21 +22,21 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 coloreado!"; +/* chat link info line */ +"(from owner)" = "(del propietario)"; + /* No comment provided by engineer. */ "(new)" = "(nuevo)"; +/* chat link info line */ +"(signed)" = "(firmado)"; + /* No comment provided by engineer. */ "(this device v%@)" = "(este dispositivo v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuye](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Contacta vía email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Estrella en GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Añadir contacto**: crea un enlace de invitación nuevo."; @@ -64,6 +67,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Escanear / Pegar enlace**: para conectar mediante un enlace recibido."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Test servidor** para recibir su nombre."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; @@ -175,6 +181,18 @@ /* time interval */ "%d months" = "%d mes(es)"; +/* channel relay bar +channel subscriber relay bar */ +"%d relays failed" = "%d servidores han fallado"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays not active" = "%d servidores inactivos"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays removed" = "%d servidores eliminados"; + /* time interval */ "%d sec" = "%d segundo(s)"; @@ -184,15 +202,50 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d mensaje(s) omitido(s)"; +/* channel subscriber count */ +"%d subscriber" = "%d suscriptor"; + +/* channel subscriber count */ +"%d subscribers" = "%d suscriptores"; + /* time interval */ "%d weeks" = "%d semana(s)"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d servidores activos"; + +/* channel relay bar */ +"%d/%d relays active, %d errors" = "%1$d/%2$d servidores activos, %3$d errores"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d servidores activos, %3$d han fallado"; + +/* channel relay bar */ +"%d/%d relays active, %d removed" = "%1$d/%2$d servidores activos, %3$d servidores eliminados"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d servidores conectados"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d servidores conectados, %3$d errores"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d failed" = "%1$d/%2$d servidores conectados, %3$d con fallo"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d removed" = "%1$d/%2$d servidores conectados, %3$d eliminados"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld eventos del canal"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld contacto(s) seleccionado(s)"; @@ -200,7 +253,7 @@ "%lld file(s) with total size of %@" = "%lld archivo(s) con un tamaño total de %@"; /* No comment provided by engineer. */ -"%lld group events" = "%lld evento(s) de grupo"; +"%lld group events" = "%lld evento(s) del grupo"; /* No comment provided by engineer. */ "%lld members" = "%lld miembros"; @@ -262,6 +315,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~strike~"; +/* owner verification */ +"⚠️ Signature verification failed: %@." = "⚠️ Verificación de firma fallida: %@."; + /* time to disappear */ "0 sec" = "0 seg"; @@ -307,6 +363,9 @@ time interval */ /* No comment provided by engineer. */ "A few more things" = "Algunas cosas más"; +/* No comment provided by engineer. */ +"A link for one person to connect" = "Enlace para un solo contacto"; + /* notification title */ "A new contact" = "Contacto nuevo"; @@ -371,6 +430,9 @@ swipe action */ /* alert title */ "Accept member" = "Aceptar miembro"; +/* No comment provided by engineer. */ +"accepted" = "aceptado"; + /* rcv group event chat item */ "accepted %@" = "%@ aceptado"; @@ -392,6 +454,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Errores de confirmación"; +/* No comment provided by engineer. */ +"active" = "activo"; + /* token status text */ "Active" = "Activo"; @@ -399,7 +464,7 @@ swipe action */ "Active connections" = "Conexiones activas"; /* 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." = "Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos."; +"Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts." = "Añade la dirección a tu perfil para que tus contactos SimpleX puedan compartirla con otros. La actualización del perfil se enviará a tus contactos SimpleX."; /* No comment provided by engineer. */ "Add friends" = "Añadir amigos"; @@ -440,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "Added message servers" = "Servidores de mensajes añadidos"; +/* No comment provided by engineer. */ +"Adding relays will be supported later." = "Añadir servidores estará disponible en una versión posterior."; + /* No comment provided by engineer. */ "Additional accent" = "Acento adicional"; @@ -530,6 +598,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Todos los perfiles"; +/* No comment provided by engineer. */ +"All relays failed" = "Todos los servidores han fallado"; + +/* No comment provided by engineer. */ +"All relays removed" = "Todos los servidores eliminados"; + /* No comment provided by engineer. */ "All reports will be archived for you." = "Todos los informes serán archivados para ti."; @@ -566,6 +640,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite. (24 horas)"; +/* No comment provided by engineer. */ +"Allow members to chat with admins." = "Permitir que los miembros chateen con administradores."; + /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Se permiten las reacciones a los mensajes pero sólo si tu contacto también las permite."; @@ -575,12 +652,18 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Se permiten mensajes directos entre miembros."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Se permiten mensajes directos entre suscriptores."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Permites el envío de mensajes temporales."; /* No comment provided by engineer. */ "Allow sharing" = "Permitir compartir"; +/* No comment provided by engineer. */ +"Allow subscribers to chat with admins." = "Permitir que los suscriptores chateen con administradores."; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Se permite la eliminación irreversible de mensajes. (24 horas)"; @@ -650,9 +733,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Responder llamada"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Cualquiera puede alojar servidores."; - /* No comment provided by engineer. */ "App build: %@" = "Compilación app: %@"; @@ -794,6 +874,15 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "ID de mensaje incorrecto"; +/* No comment provided by engineer. */ +"Be free\nin your network" = "Se libre\nen tu red"; + +/* No comment provided by engineer. */ +"Be free in your network." = "Se libre en tu red."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Porque hemos destruido el poder de saber quien eres. De manera que tu poder nunca se pueda arrebatar."; + /* No comment provided by engineer. */ "Better calls" = "Llamadas mejoradas"; @@ -851,6 +940,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "¿Bloquear miembro?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "¿Bloquear al suscriptor para todos?"; + /* marked deleted chat item preview text */ "blocked" = "bloqueado"; @@ -895,9 +987,15 @@ marked deleted chat item preview text */ "Both you and your contact can send voice messages." = "Tanto tú como tu contacto podéis enviar mensajes de voz."; /* No comment provided by engineer. */ -"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Búlgaro, Finlandés, Tailandés y Ucraniano - gracias a los usuarios y [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +"Bottom bar" = "Barra inferior"; + +/* compose placeholder for channel owner */ +"Broadcast" = "Emisión"; /* No comment provided by engineer. */ +"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Búlgaro, Finlandés, Tailandés y Ucraniano - gracias a los usuarios y [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* chat link info line */ "Business address" = "Dirección empresarial"; /* No comment provided by engineer. */ @@ -912,9 +1010,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios – spam prohibido."; - /* No comment provided by engineer. */ "call" = "llamada"; @@ -939,6 +1034,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Camera not available" = "Cámara no disponible"; +/* No comment provided by engineer. */ +"can't broadcast" = "no puedes retransmitir"; + /* No comment provided by engineer. */ "Can't call contact" = "No se puede llamar al contacto"; @@ -1038,6 +1136,58 @@ set passcode view */ /* chat item text */ "changing address…" = "cambiando de servidor…"; +/* shown as sender role for channel messages */ +"channel" = "canal"; + +/* No comment provided by engineer. */ +"Channel" = "Canal"; + +/* No comment provided by engineer. */ +"Channel display name" = "Título mostrado del canal"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Título completo del canal (opcional)"; + +/* alert message +alert subtitle */ +"Channel has no active relays. Please try to join later." = "El canal no tiene servidores activos. Por favor, intenta unirte más tarde."; + +/* No comment provided by engineer. */ +"Channel image" = "Imagen del canal"; + +/* chat link info line */ +"Channel link" = "Enlace del canal"; + +/* No comment provided by engineer. */ +"Channel preferences" = "Preferencias del canal"; + +/* No comment provided by engineer. */ +"Channel profile" = "Perfil del canal"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "El perfil del canal se almacena en los dispositivos de los suscriptores y en los servidores de chat."; + +/* snd group event chat item */ +"channel profile updated" = "perfil del canal actualizado"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "El perfil del canal ha sido modificado. Si lo guardas, el perfil actualizado será enviado a los suscriptores."; + +/* alert title */ +"Channel temporarily unavailable" = "Canales no disponibles temporalmente"; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "El canal será eliminado para todos los suscriptores. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "El canal será eliminado para tí. ¡No puede deshacerse!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "El canal comenzará a funcionar con %1$d de %2$d servidores. ¿Continuar?"; + +/* No comment provided by engineer. */ +"Channels" = "Canales"; + /* No comment provided by engineer. */ "Chat" = "Chat"; @@ -1089,6 +1239,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Perfil de usuario"; +/* No comment provided by engineer. */ +"Chat relay" = "Servidor de chat"; + +/* No comment provided by engineer. */ +"Chat relays" = "Servidores de chat"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "Los servidores de chat reenvían los mensajes en los canales que has creado."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "Los servidores de chat reenvían los mensajes a los suscriptores del canal."; + /* No comment provided by engineer. */ "Chat theme" = "Tema de chat"; @@ -1098,7 +1260,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No puede deshacerse!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chatea con administradores"; /* No comment provided by engineer. */ @@ -1110,15 +1273,30 @@ set passcode view */ /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Chats with admins are prohibited." = "Chat con administradores no permitido."; + +/* alert message */ +"Chats with admins in public channels have no E2E encryption - use only with trusted chat relays." = "El chat con administradores en el canal público no dispone de cifrado E2E. Úsalo sólo con servidores de confianza."; + /* No comment provided by engineer. */ "Chats with members" = "Chat con miembros"; +/* No comment provided by engineer. */ +"Chats with members are disabled" = "Chats con miembros desactivado"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Comprobar mensajes cada 20 min."; /* No comment provided by engineer. */ "Check messages when allowed." = "Comprobar mensajes cuando se permita."; +/* alert message */ +"Check relay address and try again." = "Comprueba la dirección del servidor y prueba de nuevo."; + +/* alert message */ +"Check relay name and try again." = "Comprueba el nombre del servidor y prueba de nuevo."; + /* alert title */ "Check server address and try again." = "Comprueba la dirección del servidor e inténtalo de nuevo."; @@ -1213,7 +1391,7 @@ set passcode view */ "Configure ICE servers" = "Configure servidores ICE"; /* No comment provided by engineer. */ -"Configure server operators" = "Configurar operadores de servidores"; +"Configure relays" = "Configurar servidores"; /* No comment provided by engineer. */ "Confirm" = "Confirmar"; @@ -1279,6 +1457,9 @@ server test step */ /* new chat sheet title */ "Connect via link" = "Conectar mediante enlace"; +/* No comment provided by engineer. */ +"Connect via link or QR code" = "Conecta vía enlace o QR"; + /* new chat sheet title */ "Connect via one-time link" = "Conectar mediante enlace de un sólo uso"; @@ -1348,12 +1529,15 @@ server test step */ /* alert title */ "Connection error" = "Error conexión"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Error de conexión (Autenticación)"; /* chat list item title (it should not be shown */ "connection established" = "conexión establecida"; +/* No comment provided by engineer. */ +"Connection failed" = "Conexión fallida"; + /* No comment provided by engineer. */ "Connection is blocked by server operator:\n%@" = "Conexión bloqueada por el operador del servidor:\n%@"; @@ -1390,6 +1574,9 @@ server test step */ /* profile update event chat item */ "contact %@ changed to %@" = "el contacto %1$@ ha cambiado a %2$@"; +/* chat link info line */ +"Contact address" = "Dirección de contacto"; + /* No comment provided by engineer. */ "Contact allows" = "El contacto permite"; @@ -1450,6 +1637,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Continuar"; +/* No comment provided by engineer. */ +"Contribute" = "Contribuye"; + /* No comment provided by engineer. */ "Conversation deleted!" = "¡Conversación eliminada!"; @@ -1468,9 +1658,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "¿Corregir el nombre a %@?"; -/* No comment provided by engineer. */ -"Create" = "Crear"; - /* No comment provided by engineer. */ "Create 1-time link" = "Crear enlace de un solo uso"; @@ -1498,6 +1685,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Crear perfil"; +/* No comment provided by engineer. */ +"Create public channel" = "Crear canal público"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Crear canal público (BETA)"; + /* server test step */ "Create queue" = "Crear cola"; @@ -1507,9 +1700,15 @@ server test step */ /* No comment provided by engineer. */ "Create your address" = "Crea tu dirección"; +/* No comment provided by engineer. */ +"Create your link" = "Crea tu enlace"; + /* No comment provided by engineer. */ "Create your profile" = "Crea tu perfil"; +/* No comment provided by engineer. */ +"Create your public address" = "Crea tu dirección pública"; + /* No comment provided by engineer. */ "Created" = "Creadas"; @@ -1522,6 +1721,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Creando enlace al archivo"; +/* No comment provided by engineer. */ +"Creating channel" = "Creando canal"; + /* No comment provided by engineer. */ "Creating link…" = "Creando enlace…"; @@ -1624,8 +1826,8 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Informe debug"; -/* No comment provided by engineer. */ -"Decentralized" = "Descentralizada"; +/* relay test step */ +"Decode link" = "Decodificar enlace"; /* message decrypt error item */ "Decryption error" = "Error descifrado"; @@ -1668,6 +1870,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Eliminar y notificar contacto"; +/* No comment provided by engineer. */ +"Delete channel" = "Eliminar canal"; + +/* No comment provided by engineer. */ +"Delete channel?" = "¿Eliminar el canal?"; + /* No comment provided by engineer. */ "Delete chat" = "Eliminar chat"; @@ -1771,6 +1979,9 @@ alert button */ /* server test step */ "Delete queue" = "Eliminar cola"; +/* No comment provided by engineer. */ +"Delete relay" = "Eliminar servidor"; + /* No comment provided by engineer. */ "Delete report" = "Eliminar informe"; @@ -1795,6 +2006,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Eliminado: %@"; +/* rcv group event chat item */ +"deleted channel" = "canal eliminado"; + /* rcv direct event chat item */ "deleted contact" = "contacto eliminado"; @@ -1885,6 +2099,12 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "Los mensajes directos entre miembros del grupo no están permitidos."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "Los mensajes directos entre suscriptores del canal no están permitidos."; + +/* alert button */ +"Disable" = "Desactivar"; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Desactivar (conservando anulaciones)"; @@ -1892,7 +2112,7 @@ alert button */ "Disable automatic message deletion?" = "¿Desactivar la eliminación automática de mensajes?"; /* alert button */ -"Disable delete messages" = "Desactivar"; +"Disable delete messages" = "Desactivar eliminar mensajes"; /* No comment provided by engineer. */ "Disable for all" = "Desactivar para todos"; @@ -1942,6 +2162,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "No se envía el historial a los miembros nuevos."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "No se envía el historial a los suscriptores nuevos."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado."; @@ -2021,27 +2244,39 @@ chat item action */ /* No comment provided by engineer. */ "E2E encrypted notifications." = "Notificaciones cifradas E2E."; +/* No comment provided by engineer. */ +"Easier to invite your friends 👋" = "Invitar a tus amigos es más fácil 👋"; + /* chat item action */ "Edit" = "Editar"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Editar perfil del canal"; + /* No comment provided by engineer. */ "Edit group profile" = "Editar perfil de grupo"; /* No comment provided by engineer. */ "Empty message!" = "¡Mensaje vacío!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Activar"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Activar (conservar anulaciones)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Activar al menos un servidor de chat en Servidores y Redes."; + /* alert title */ "Enable automatic message deletion?" = "¿Activar eliminación automática de mensajes?"; /* No comment provided by engineer. */ "Enable camera access" = "Permitir acceso a la cámara"; +/* alert title */ +"Enable chats with admins?" = "¿Activar chat con administradores?"; + /* No comment provided by engineer. */ "Enable disappearing messages by default." = "Activa por defecto los mensajes temporales."; @@ -2057,11 +2292,11 @@ chat item action */ /* No comment provided by engineer. */ "Enable instant notifications?" = "¿Activar notificaciones instantáneas?"; -/* No comment provided by engineer. */ -"Enable lock" = "Activar bloqueo"; +/* alert title */ +"Enable link previews?" = "¿Activar previsualización de enlaces?"; /* No comment provided by engineer. */ -"Enable notifications" = "Activar notificaciones"; +"Enable lock" = "Activar bloqueo"; /* No comment provided by engineer. */ "Enable periodic notifications?" = "¿Activar notificaciones periódicas?"; @@ -2168,6 +2403,9 @@ chat item action */ /* call status */ "ended call %@" = "llamada finalizada %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Introduce el título del canal…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Introduce la contraseña correcta."; @@ -2186,6 +2424,12 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "¡Introduce la contraseña arriba para mostrar!"; +/* No comment provided by engineer. */ +"Enter profile name..." = "Introduce el nombre del perfil…"; + +/* No comment provided by engineer. */ +"Enter relay name…" = "Introduce el nombre del servidor…"; + /* No comment provided by engineer. */ "Enter server manually" = "Añadir manualmente"; @@ -2204,7 +2448,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "error"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Error"; /* No comment provided by engineer. */ @@ -2222,6 +2466,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; +/* alert title */ +"Error adding relay" = "Error al añadir el servidor"; + /* alert title */ "Error adding server" = "Error al añadir servidor"; @@ -2258,6 +2505,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Error al crear dirección"; +/* alert title */ +"Error creating channel" = "Error al crear el canal"; + /* No comment provided by engineer. */ "Error creating group" = "Error al crear grupo"; @@ -2363,6 +2613,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Error al restablecer las estadísticas"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Error al guardar el perfil del canal"; + /* alert title */ "Error saving chat list" = "Error al guardar listas"; @@ -2405,6 +2658,9 @@ chat item action */ /* No comment provided by engineer. */ "Error setting delivery receipts!" = "¡Error al configurar confirmaciones de entrega!"; +/* alert title */ +"Error sharing channel" = "Error al compartir el canal"; + /* No comment provided by engineer. */ "Error starting chat" = "Error al iniciar Chat"; @@ -2447,6 +2703,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Error: "; +/* receive error chat item */ +"error: %@" = "error: %@"; + /* alert message file error text snd error text */ @@ -2501,6 +2760,9 @@ server test error */ /* No comment provided by engineer. */ "Exporting database archive…" = "Exportando base de datos…"; +/* No comment provided by engineer. */ +"failed" = "fallo"; + /* No comment provided by engineer. */ "Failed to remove passphrase" = "Error al eliminar la contraseña"; @@ -2628,6 +2890,9 @@ server test error */ /* No comment provided by engineer. */ "For all moderators" = "Para todos los moderadores"; +/* No comment provided by engineer. */ +"For anyone to reach you" = "Cualquiera puede contactarte"; + /* servers error servers warning */ "For chat profile %@:" = "Para el perfil de chat %@:"; @@ -2713,9 +2978,15 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Reducción consumo de batería"; +/* relay test step */ +"Get link" = "Recibir el enlace"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Las menciones ahora se notifican."; +/* No comment provided by engineer. */ +"Get started" = "Empezar"; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIFs y stickers"; @@ -2761,7 +3032,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "el grupo ha sido eliminado"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Enlace de grupo"; /* No comment provided by engineer. */ @@ -2833,6 +3104,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "El historial no se envía a miembros nuevos."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "El historial no se envía a suscriptores nuevos."; + /* time unit */ "hours" = "horas"; @@ -2872,6 +3146,9 @@ servers warning */ /* No comment provided by engineer. */ "If you enter your self-destruct passcode while opening the app:" = "Si al abrir la aplicación introduces el código de autodestrucción:"; +/* down migration warning */ +"If you joined or created channels, they will stop working permanently." = "Si te has unido o has creado canales, dejarán de funcionar permanentemente."; + /* No comment provided by engineer. */ "If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Si necesitas usar el chat ahora pulsa **Hacerlo más tarde** más abajo (se ofrecerá migrar la base de datos cuando se reinicie la aplicación)."; @@ -2890,9 +3167,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Inmediatamente"; -/* No comment provided by engineer. */ -"Immune to spam" = "Inmune a spam y abuso"; - /* No comment provided by engineer. */ "Import" = "Importar"; @@ -2993,7 +3267,7 @@ servers warning */ "Initial role" = "Rol inicial"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Instalar terminal para SimpleX Chat"; /* No comment provided by engineer. */ "Instant" = "Al instante"; @@ -3028,7 +3302,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "datos Chat no válidos"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Enlace de conexión no válido"; /* invalid chat item */ @@ -3049,6 +3323,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Código QR no válido"; +/* alert title */ +"Invalid relay address!" = "¡Dirección de servidor no válido!"; + +/* alert title */ +"Invalid relay name!" = "¡Nombre de servidor no válido!"; + /* No comment provided by engineer. */ "Invalid response" = "Respuesta no válida"; @@ -3076,6 +3356,9 @@ servers warning */ /* No comment provided by engineer. */ "Invite members" = "Invitar miembros"; +/* No comment provided by engineer. */ +"Invite someone privately" = "Invitación privada"; + /* No comment provided by engineer. */ "Invite to chat" = "Invitar al chat"; @@ -3142,6 +3425,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "Unirme como %@"; +/* No comment provided by engineer. */ +"Join channel" = "Unirme al canal"; + /* new chat sheet title */ "Join group" = "Unirme al grupo"; @@ -3190,6 +3476,12 @@ servers warning */ /* swipe action */ "Leave" = "Salir"; +/* No comment provided by engineer. */ +"Leave channel" = "Salir del canal"; + +/* No comment provided by engineer. */ +"Leave channel?" = "¿Salir del canal?"; + /* No comment provided by engineer. */ "Leave chat" = "Salir del chat"; @@ -3208,6 +3500,9 @@ servers warning */ /* No comment provided by engineer. */ "Less traffic on mobile networks." = "Menos tráfico en redes móviles."; +/* No comment provided by engineer. */ +"Let someone connect to you" = "Conecta con alguien"; + /* email subject */ "Let's talk in SimpleX Chat" = "Hablemos en SimpleX Chat"; @@ -3217,9 +3512,15 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Limitaciones"; +/* No comment provided by engineer. */ +"link" = "enlace"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "¡Enlazar aplicación móvil con ordenador! 🔗"; +/* owner verification */ +"Link signature verified." = "Firma del enlace verificada."; + /* No comment provided by engineer. */ "Linked desktop options" = "Opciones ordenador enlazado"; @@ -3349,6 +3650,9 @@ servers warning */ /* No comment provided by engineer. */ "Members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; +/* No comment provided by engineer. */ +"Members can chat with admins." = "Los miembros pueden chatear con los administradores."; + /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; @@ -3391,6 +3695,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Borrador de mensaje"; +/* No comment provided by engineer. */ +"Message error" = "Mensaje de error"; + /* item status text */ "Message forwarded" = "Mensaje reenviado"; @@ -3451,6 +3758,12 @@ servers warning */ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "¡Los mensajes nuevos de %@ serán mostrados!"; +/* No comment provided by engineer. */ +"Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages." = "Los mensajes en este canal **no están cifrados de extremo a extremo**. Los servidores pueden ver estos mensajes."; + +/* E2EE info chat item */ +"Messages in this channel are not end-to-end encrypted. Chat relays can see these messages." = "Los mensajes en este canal no están cifrados de extremo a extremo. Los servidores pueden ver estos mensajes."; + /* alert message */ "Messages in this chat will never be deleted." = "Los mensajes de esta conversación nunca se eliminan."; @@ -3470,10 +3783,10 @@ servers warning */ "Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo resistente a tecnología cuántica** con secreto perfecto hacía adelante, repudio y recuperación tras ataque."; /* No comment provided by engineer. */ -"Migrate device" = "Migrar dispositivo"; +"Migrate" = "Migrar"; /* No comment provided by engineer. */ -"Migrate from another device" = "Migrar desde otro dispositivo"; +"Migrate device" = "Migrar dispositivo"; /* No comment provided by engineer. */ "Migrate here" = "Migrar aquí"; @@ -3565,12 +3878,18 @@ servers warning */ /* No comment provided by engineer. */ "Network & servers" = "Servidores y Redes"; +/* No comment provided by engineer. */ +"Network commitments" = "Compromisos en la red"; + /* No comment provided by engineer. */ "Network connection" = "Conexión de red"; /* No comment provided by engineer. */ "Network decentralization" = "Descentralización de la red"; +/* conn error description */ +"Network error" = "Error de red"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Problema en la red - el mensaje ha expirado tras muchos intentos de envío."; @@ -3580,6 +3899,9 @@ servers warning */ /* No comment provided by engineer. */ "Network operator" = "Operador de red"; +/* No comment provided by engineer. */ +"Network routers cannot know\nwho talks to whom" = "Los routers de la red no pueden saber\nquién se comunica con quién"; + /* No comment provided by engineer. */ "Network settings" = "Configuración de red"; @@ -3589,15 +3911,24 @@ servers warning */ /* delete after time */ "never" = "nunca"; +/* No comment provided by engineer. */ +"new" = "nuevo"; + /* token status text */ "New" = "Nuevo"; +/* No comment provided by engineer. */ +"New 1-time link" = "Nuevo enlace de 1 solo uso"; + /* No comment provided by engineer. */ "New chat" = "Nuevo chat"; /* No comment provided by engineer. */ "New chat experience 🎉" = "Nueva experiencia de chat 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Nuevo servidor de chat"; + /* notification */ "New contact request" = "Nueva solicitud de contacto"; @@ -3655,9 +3986,21 @@ servers warning */ /* No comment provided by engineer. */ "No" = "No"; +/* No comment provided by engineer. */ +"No account. No phone. No email. No ID.\nThe most secure encryption." = "Sin cuenta. Sin teléfono. Sin email. Sin ID.\nEl cifrado más seguro."; + +/* No comment provided by engineer. */ +"No active relays" = "Sin servidores activos"; + /* Authentication unavailable */ "No app password" = "Sin contraseña de la aplicación"; +/* No comment provided by engineer. */ +"No chat relays" = "Sin servidores de chat"; + +/* servers warning */ +"No chat relays enabled." = "Ningún servidor de chat activado."; + /* No comment provided by engineer. */ "No chats" = "Sin chats"; @@ -3755,7 +4098,16 @@ servers warning */ "No unread chats" = "Ningún chat sin leer"; /* No comment provided by engineer. */ -"No user identifiers." = "Sin identificadores de usuario."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nadie monitorizaba tus conversaciones. Nadie registraba tus ubicaciones. La privacidad nunca fue un lujo, era la manera de vivir."; + +/* No comment provided by engineer. */ +"Non-profit governance" = "Gobernanza no lucrativa"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "No un candado mejorado en la puerta de otro. No un terrateniente que respeta tu privacidad pero sigue guardando un registro de tus visitantes. Tu no eres el invitado. Estás en tu casa y ningún rey podrá entrar. Tu eres el soberano."; + +/* alert title */ +"Not all relays connected" = "Hay servidores no conectados"; /* No comment provided by engineer. */ "Not compatible!" = "¡No compatible!"; @@ -3813,7 +4165,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3822,9 +4174,15 @@ new chat action */ /* group pref value */ "on" = "Activado"; +/* No comment provided by engineer. */ +"On your phone, not on servers." = "En tu teléfono, no en algún servidor."; + /* No comment provided by engineer. */ "One-time invitation link" = "Enlace de invitación de un solo uso"; +/* chat link info line */ +"One-time link" = "Enlace de un solo uso"; + /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Se **requieren** hosts .onion para la conexión.\nRequiere activación de la VPN."; @@ -3834,6 +4192,9 @@ new chat action */ /* No comment provided by engineer. */ "Onion hosts will not be used." = "No se usarán hosts .onion."; +/* No comment provided by engineer. */ +"Only channel owners can change channel preferences." = "Sólo los propietarios pueden modificar las preferencias de los canales."; + /* No comment provided by engineer. */ "Only chat owners can change preferences." = "Sólo los propietarios del chat pueden cambiar las preferencias."; @@ -3894,12 +4255,16 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sólo tu contacto puede enviar mensajes de voz."; -/* alert action */ +/* alert action +alert button */ "Open" = "Abrir"; /* No comment provided by engineer. */ "Open changes" = "Abrir cambios"; +/* new chat action */ +"Open channel" = "Abrir canal"; + /* new chat action */ "Open chat" = "Abrir chat"; @@ -3912,6 +4277,9 @@ new chat action */ /* No comment provided by engineer. */ "Open conditions" = "Abrir condiciones"; +/* alert title */ +"Open external link?" = "¿Abrir enlace externo?"; + /* alert action */ "Open full link" = "Abrir enlace completo"; @@ -3924,6 +4292,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Abrir menú migración a otro dispositivo"; +/* new chat action */ +"Open new channel" = "Abrir canal nuevo"; + /* new chat action */ "Open new chat" = "Abrir chat nuevo"; @@ -3954,6 +4325,9 @@ new chat action */ /* alert title */ "Operator server" = "Servidor del operador"; +/* No comment provided by engineer. */ +"Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code" = "Los operadores se comprometen a:\n- Ser independientes\n- Minimizar el tratamiento de metadatos\n- Ejecutar código open-source verificado"; + /* No comment provided by engineer. */ "Or import archive file" = "O importa desde un archivo"; @@ -3966,12 +4340,18 @@ new chat action */ /* No comment provided by engineer. */ "Or securely share this file link" = "O comparte de forma segura este enlace al archivo"; +/* No comment provided by engineer. */ +"Or show QR in person or via video call." = "O muestra el código QR en persona o por videollamada."; + /* No comment provided by engineer. */ "Or show this code" = "O muestra este código"; /* No comment provided by engineer. */ "Or to share privately" = "O para compartir en privado"; +/* No comment provided by engineer. */ +"Or use this QR - print or show online." = "O usa el QR, imprímelo o muestralo en línea."; + /* No comment provided by engineer. */ "Organize chats into lists" = "Organiza tus chats en listas"; @@ -3990,9 +4370,18 @@ new chat action */ /* member role */ "owner" = "propietario"; +/* No comment provided by engineer. */ +"Owner" = "Propietario"; + /* feature role */ "owners" = "propietarios"; +/* No comment provided by engineer. */ +"Owners" = "Propietarios"; + +/* No comment provided by engineer. */ +"Ownership: you can run your own relays." = "En propiedad: puedes poner en marcha tus propios servidores."; + /* No comment provided by engineer. */ "Passcode" = "Código de acceso"; @@ -4020,6 +4409,9 @@ new chat action */ /* No comment provided by engineer. */ "Paste image" = "Pegar imagen"; +/* No comment provided by engineer. */ +"Paste link / Scan" = "Pegar enlace / Escanear"; + /* No comment provided by engineer. */ "Paste link to connect!" = "Pegar enlace para conectar!"; @@ -4128,6 +4520,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; +/* No comment provided by engineer. */ +"Preset relay address" = "Direcciones predefinidas"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Nombres predefinidos"; + /* No comment provided by engineer. */ "Preset server address" = "Dirección predefinida del servidor"; @@ -4150,10 +4548,10 @@ new chat action */ "Privacy policy and conditions of use." = "Política de privacidad y condiciones de uso."; /* No comment provided by engineer. */ -"Privacy redefined" = "Privacidad redefinida"; +"Privacy: for owners and subscribers." = "Privacidad: para propietarios y suscriptores."; /* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores."; +"Private and secure messaging." = "Mensajería segura y privada."; /* No comment provided by engineer. */ "Private filenames" = "Nombres de archivos privados"; @@ -4179,6 +4577,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Timeout enrutamiento privado"; +/* alert action */ +"Proceed" = "Continuar"; + /* No comment provided by engineer. */ "Profile and server connections" = "Eliminar perfil y conexiones"; @@ -4195,11 +4596,14 @@ new chat action */ "Profile theme" = "Tema del perfil"; /* alert message */ -"Profile update will be sent to your contacts." = "La actualización del perfil se enviará a tus contactos."; +"Profile update will be sent to your SimpleX contacts." = "La actualización del perfil se enviará a tus contactos SimpleX."; /* No comment provided by engineer. */ "Prohibit audio/video calls." = "No se permiten llamadas y videollamadas."; +/* No comment provided by engineer. */ +"Prohibit chats with admins." = "El chat con los administradores no está permitido."; + /* No comment provided by engineer. */ "Prohibit irreversible message deletion." = "No se permite la eliminación irreversible de mensajes."; @@ -4215,6 +4619,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "No se permiten mensajes directos entre miembros."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "No se permiten mensajes directos entre suscriptores."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "No se permiten mensajes temporales."; @@ -4257,6 +4664,9 @@ new chat action */ /* No comment provided by engineer. */ "Proxy requires password" = "El proxy requiere contraseña"; +/* No comment provided by engineer. */ +"Public channels - speak freely 🚀" = "Canales públicos - habla con libertad 🚀"; + /* No comment provided by engineer. */ "Push notifications" = "Notificaciones push"; @@ -4285,16 +4695,10 @@ new chat action */ "Read more" = "Saber más"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Conoce más en nuestro repositorio GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Conoce más en el [Manual del Usuario](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)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Conoce más en la Guía del Usuario."; /* No comment provided by engineer. */ "Receipts are disabled" = "Las confirmaciones están desactivadas"; @@ -4403,12 +4807,36 @@ swipe action */ /* call status */ "rejected call" = "llamada rechazada"; +/* member role */ +"relay" = "servidor"; + +/* No comment provided by engineer. */ +"Relay" = "Servidor"; + +/* alert title */ +"Relay address" = "Dirección del servidor"; + +/* alert title */ +"Relay connection failed" = "La conexión con el servidor ha fallado"; + +/* No comment provided by engineer. */ +"Relay link" = "Enlace servidor"; + +/* alert message */ +"Relay results:" = "Resultados del servidor:"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "El servidor de retransmisión protege tu IP pero puede ver la duración de la llamada."; +/* No comment provided by engineer. */ +"Relay test failed!" = "¡El test del servidor ha fallado!"; + +/* No comment provided by engineer. */ +"Reliability: many relays per channel." = "Fiabilidad: muchos servidores por canal."; + /* alert action */ "Remove" = "Eliminar"; @@ -4433,12 +4861,24 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "¿Eliminar contraseña de Keychain?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Eliminar suscriptor"; + +/* alert title */ +"Remove subscriber?" = "¿Eliminar suscriptor?"; + /* No comment provided by engineer. */ "removed" = "expulsado"; +/* receive error chat item */ +"removed (%d attempts)" = "eliminado (%d intentos)"; + /* rcv group event chat item */ "removed %@" = "ha expulsado a %@"; +/* No comment provided by engineer. */ +"removed by operator" = "eliminado por el operador"; + /* profile update event chat item */ "removed contact address" = "dirección de contacto eliminada"; @@ -4607,6 +5047,9 @@ swipe action */ /* No comment provided by engineer. */ "Run chat" = "Ejecutar SimpleX"; +/* No comment provided by engineer. */ +"Safe web links" = "Enlaces web seguros"; + /* No comment provided by engineer. */ "Safely receive files" = "Recibe archivos de forma segura"; @@ -4623,6 +5066,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Guardar (y notificar miembros)"; +/* alert button */ +"Save (and notify subscribers)" = "Guardar (y notificar suscriptores)"; + /* alert title */ "Save admission settings?" = "¿Guardar configuración?"; @@ -4632,12 +5078,21 @@ chat item action */ /* No comment provided by engineer. */ "Save and notify group members" = "Guardar y notificar grupo"; +/* No comment provided by engineer. */ +"Save and notify subscribers" = "Guardar y notificar suscriptores"; + /* No comment provided by engineer. */ "Save and reconnect" = "Guardar y reconectar"; /* No comment provided by engineer. */ "Save and update group profile" = "Guardar y actualizar perfil del grupo"; +/* No comment provided by engineer. */ +"Save channel profile" = "Guardar perfil del canal"; + +/* alert title */ +"Save channel profile?" = "¿Guardar perfil del canal?"; + /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; @@ -4767,6 +5222,9 @@ chat item action */ /* chat item text */ "security code changed" = "código de seguridad cambiado"; +/* No comment provided by engineer. */ +"Security: owners hold channel keys." = "Seguridad: los propietarios tienen la llave del canal."; + /* chat item action */ "Select" = "Seleccionar"; @@ -4845,12 +5303,18 @@ chat item action */ /* No comment provided by engineer. */ "Send request without message" = "Enviar solicitud sin mensaje"; +/* No comment provided by engineer. */ +"Send the link via any messenger - it's secure. Ask to paste into SimpleX." = "Envía el enlace con cualquier mensajero, es seguro. El contacto debe pegarlo en SimpleX."; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Envíalos desde la galería o desde teclados personalizados."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Se envían hasta 100 mensajes más recientes a los miembros nuevos."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Se envían hasta 100 mensajes más recientes a los suscriptores nuevos."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Envía tu comentario privado a los grupos."; @@ -4860,6 +5324,9 @@ chat item action */ /* No comment provided by engineer. */ "Sender may have deleted the connection request." = "El remitente puede haber eliminado la solicitud de conexión."; +/* alert message */ +"Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later." = "Enviar una previsualización del enlace puede revelar tu dirección IP al sitio web. Puedes cambiarlo más tarde en los ajustes de privacidad."; + /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "El envío de confirmaciones de entrega se activará para todos los contactos en todos los perfiles visibles."; @@ -4938,6 +5405,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "información cola del servidor: %1$@\n\núltimo mensaje recibido: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "El servidor requiere autorización para conectar con el servidor, comprueba la contraseña."; + /* server test error */ "Server requires authorization to create queues, check password." = "El servidor requiere autorización para crear colas, comprueba la contraseña."; @@ -5022,6 +5492,12 @@ chat item action */ /* alert message */ "Settings were changed." = "La configuración ha sido modificada."; +/* No comment provided by engineer. */ +"Setup notifications" = "Configurar notificaciones"; + +/* No comment provided by engineer. */ +"Setup routers" = "Configurar routers"; + /* No comment provided by engineer. */ "Shape profile images" = "Dar forma a las imágenes de perfil"; @@ -5042,7 +5518,10 @@ chat item action */ "Share address publicly" = "Campartir dirección públicamente"; /* alert title */ -"Share address with contacts?" = "¿Compartir la dirección con los contactos?"; +"Share address with SimpleX contacts?" = "¿Compartir la dirección con los contactos SimpleX?"; + +/* No comment provided by engineer. */ +"Share channel" = "Compartir canal"; /* No comment provided by engineer. */ "Share from other apps." = "Comparte desde otras aplicaciones."; @@ -5059,6 +5538,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Perfil a compartir"; +/* No comment provided by engineer. */ +"Share relay address" = "Compartir dirección del servidor"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Comparte tu dirección SimpleX en redes sociales."; @@ -5069,7 +5551,10 @@ chat item action */ "Share to SimpleX" = "Compartir con Simplex"; /* No comment provided by engineer. */ -"Share with contacts" = "Compartir con contactos"; +"Share via chat" = "Compartir mediante chat"; + +/* No comment provided by engineer. */ +"Share with SimpleX contacts" = "Compartir con contactos SimpleX"; /* No comment provided by engineer. */ "Share your address" = "Comparte tu dirección"; @@ -5173,6 +5658,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Protocolos de SimpleX auditados por Trail of Bits."; +/* simplex link type */ +"SimpleX relay address" = "Dirección de servidor SimpleX"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modo incógnito simplificado"; @@ -5225,6 +5713,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "cifrado estándar de extremo a extremo"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Estrella en GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Iniciar chat"; @@ -5291,6 +5782,48 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Suscritas"; +/* No comment provided by engineer. */ +"Subscriber" = "Suscriptor"; + +/* chat feature */ +"Subscriber reports" = "Informes de suscriptores"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "El suscriptor será eliminado del canal. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Suscriptores"; + +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Los suscriptores pueden añadir reacciones a los mensajes."; + +/* No comment provided by engineer. */ +"Subscribers can chat with admins." = "Los suscriptores pueden chatear con los administradores."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Los suscriptores del canal pueden eliminar mensajes de forma irreversible. (24 horas)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Los suscriptores pueden informar de mensajes a los moderadores."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Los suscriptores del canal pueden enviar mensajes directos."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Los suscriptores del canal pueden enviar mensajes temporales."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Los suscriptores del canal pueden enviar archivos y multimedia."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Los suscriptores del canal pueden enviar enlaces SimpleX."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Los suscriptores del canal pueden enviar mensajes de voz."; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Los suscriptores usan el enlace del servidor para conectarse a los canales.\nLa dirección del servidor se usó para establecer el servidor para el canal."; + /* No comment provided by engineer. */ "Subscription errors" = "Errores de suscripción"; @@ -5318,6 +5851,9 @@ report reason */ /* No comment provided by engineer. */ "Take picture" = "Hacer foto"; +/* No comment provided by engineer. */ +"Talk to someone" = "Para comunicarte"; + /* No comment provided by engineer. */ "Tap button " = "Pulsa el botón "; @@ -5331,7 +5867,7 @@ report reason */ "Tap Connect to use bot" = "Pulsa Conectar para usar el bot"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Pulsa Crear dirección SimpleX en el menú para crearla más tarde."; +"Tap Join channel" = "Pulsa Unirme al canal"; /* No comment provided by engineer. */ "Tap Join group" = "Pulsa Unirme al grupo"; @@ -5348,6 +5884,9 @@ report reason */ /* No comment provided by engineer. */ "Tap to join incognito" = "Pulsa para unirte en modo incógnito"; +/* No comment provided by engineer. */ +"Tap to open" = "Pulsa para abrir"; + /* No comment provided by engineer. */ "Tap to paste link" = "Pulsa aquí para pegar el enlace"; @@ -5385,6 +5924,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Probar notificaciones"; +/* No comment provided by engineer. */ +"Test relay" = "Test servidor"; + /* No comment provided by engineer. */ "Test server" = "Probar servidor"; @@ -5412,6 +5954,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "La aplicación protege tu privacidad mediante el uso de diferentes operadores en cada conversación."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "La app ha eliminado el mensaje tras %lld intentos de recibirlo."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion)."; @@ -5421,6 +5966,9 @@ server test failure */ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "El código QR escaneado no es un enlace de SimpleX."; +/* conn error description */ +"The connection reached the limit of undelivered messages" = "La conexión ha alcanzado al límite de mensajes no entregados"; + /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado."; @@ -5437,7 +5985,7 @@ server test failure */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "El cifrado funciona y un cifrado nuevo no es necesario. ¡Podría dar lugar a errores de conexión!"; /* No comment provided by engineer. */ -"The future of messaging" = "La nueva generación de mensajería privada"; +"The first network where you own\nyour contacts and groups." = "La primera red donde los grupos\ny los contactos son tuyos."; /* No comment provided by engineer. */ "The hash of the previous message is different." = "El hash del mensaje anterior es diferente."; @@ -5463,6 +6011,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "La libertad más antigua del ser humano, la de hablar con otra persona sin ser observado, materializada sobre una infraestructura que no puede traicionarla."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; @@ -5490,6 +6041,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Temas"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Después pasamos a internet y cada plataforma pedía una parte de tí: tu nombre, tu número, tus amistades. Aceptamos que el precio de hablar con los demás es informar a alguien de quién es interlocutor. Cada generación, personas y tecnología, ha funcionado así: teléfono, email, mensajería, redes sociales. Parecía el único camino."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Existe otro camino. Una red sin números de teléfono. Sin nombres de usuario. Sin cuentas. Sin identificadores de ningún tipo. Una red que conecta las personas y entrega mensajes cifrados sin saber quien está conectado."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Estas condiciones también se aplican para: **%@**."; @@ -5532,6 +6089,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Este grupo ya no existe."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Esto es una dirección de servidor, no puede usarse para conectar."; + +/* new chat action */ +"This is your link for channel %@!" = "Este es tu enlace para el canal %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible."; @@ -5565,6 +6128,9 @@ server test failure */ /* No comment provided by engineer. */ "To make a new connection" = "Para hacer una conexión nueva"; +/* No comment provided by engineer. */ +"To make SimpleX Network last." = "Para que la Red SimpleX perdure."; + /* No comment provided by engineer. */ "To protect against your link being replaced, you can compare contact security codes." = "Para protegerte contra una sustitución del enlace, puedes comparar los códigos de seguridad con tu contacto."; @@ -5613,9 +6179,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Para verificar el cifrado de extremo a extremo con tu contacto, compara (o escanea) el código en ambos dispositivos."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Alternar lista de chats:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Activa incógnito al conectar."; @@ -5625,6 +6188,9 @@ server test failure */ /* No comment provided by engineer. */ "Toolbar opacity" = "Opacidad barra"; +/* No comment provided by engineer. */ +"Top bar" = "Barra superior"; + /* No comment provided by engineer. */ "Total" = "Total"; @@ -5664,6 +6230,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "¿Desbloquear miembro?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "¿Desbloquear al suscriptor para todos?"; + /* rcv group event chat item */ "unblocked %@" = "ha desbloqueado a %@"; @@ -5736,12 +6305,15 @@ server test failure */ /* swipe action */ "Unread" = "No leído"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Enlace de conexión no compatible"; /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Hasta 100 últimos mensajes son enviados a los miembros nuevos."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Hasta 100 últimos mensajes son enviados a los suscriptores nuevos."; + /* No comment provided by engineer. */ "Update" = "Actualizar"; @@ -5754,6 +6326,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "¿Actualizar configuración?"; +/* rcv group event chat item */ +"updated channel profile" = "perfil del canal actualizado"; + /* No comment provided by engineer. */ "Updated conditions" = "Condiciones actualizadas"; @@ -5811,9 +6386,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Usar %@"; -/* No comment provided by engineer. */ -"Use chat" = "Usar Chat"; - /* new chat action */ "Use current profile" = "Usar perfil actual"; @@ -5823,6 +6395,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Uso para mensajes"; +/* No comment provided by engineer. */ +"Use for new channels" = "Usar para canales nuevos"; + /* No comment provided by engineer. */ "Use for new connections" = "Para conexiones nuevas"; @@ -5847,6 +6422,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Usar enrutamiento privado con servidores de mensaje desconocidos."; +/* No comment provided by engineer. */ +"Use relay" = "Usar servidor"; + /* No comment provided by engineer. */ "Use server" = "Usar servidor"; @@ -5871,6 +6449,9 @@ server test failure */ /* No comment provided by engineer. */ "Use the app with one hand." = "Usa la aplicación con una sola mano."; +/* No comment provided by engineer. */ +"Use this address in your social media profile, website, or email signature." = "Usa esta dirección en tu perfil de redes sociales, página web o firma email."; + /* No comment provided by engineer. */ "Use web port" = "Usar puerto web"; @@ -5889,6 +6470,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Verificar"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Verificar código con ordenador"; @@ -5910,6 +6494,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Comprobar código de seguridad"; +/* relay hostname */ +"via %@" = "mediante %@"; + /* No comment provided by engineer. */ "Via browser" = "Mediante navegador"; @@ -5979,9 +6566,18 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "¡Mensajes de voz no permitidos!"; +/* alert action */ +"Wait" = "Espera"; + +/* relay test step */ +"Wait response" = "Espera respuesta"; + /* No comment provided by engineer. */ "waiting for answer…" = "esperando respuesta…"; +/* No comment provided by engineer. */ +"Waiting for channel owner to add relays." = "Esperando a que el propietario del canal añada servidores."; + /* No comment provided by engineer. */ "waiting for confirmation…" = "esperando confirmación…"; @@ -6012,6 +6608,9 @@ server test failure */ /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Atención: ¡puedes perder algunos datos!"; +/* No comment provided by engineer. */ +"We made connecting simpler for new users." = "Hemos simplificado la conexión para los usuarios nuevos."; + /* No comment provided by engineer. */ "WebRTC ICE servers" = "Servidores WebRTC ICE"; @@ -6048,6 +6647,9 @@ server test failure */ /* 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." = "Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten."; +/* No comment provided by engineer. */ +"Why SimpleX is built." = "Por qué fue creado SimpleX."; + /* No comment provided by engineer. */ "WiFi" = "WiFi"; @@ -6147,6 +6749,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "Tu rol es observador"; +/* No comment provided by engineer. */ +"you are subscriber" = "eres suscriptor"; + /* snd group event chat item */ "you blocked %@" = "has bloqueado a %@"; @@ -6190,7 +6795,10 @@ server test failure */ "You can set lock screen notification preview via settings." = "Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo."; +"You can share a link or a QR code - anybody will be able to join the channel." = "Puedes compartir un enlace o código QR. Cualquiera podrá unirse al canal."; + +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puedes compartir el enlace o código QR. Cualquiera podrá unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo."; /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Puedes compartir esta dirección con tus contactos para que puedan conectar con **%@**."; @@ -6229,10 +6837,13 @@ server test failure */ "you changed role of %@ to %@" = "has cambiado el rol de %1$@ a %2$@"; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; +"You commit to:\n- Only legal content in public groups\n- Respect other users - no spam" = "Te comprometes a:\n- Sólo contenido legal en grupos públicos\n- Respetar a los demás usuarios — no hacer spam"; /* No comment provided by engineer. */ -"You decide who can connect." = "Tu decides quién se conecta."; +"You connected to the channel via this relay link." = "Te conectaste al canal mediante este enlace de servidor."; + +/* No comment provided by engineer. */ +"You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ya has solicitado la conexión\n¿Repetir solicitud?"; @@ -6288,6 +6899,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "has desbloqueado a %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Naciste sin una cuenta"; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Podrás enviar mensajes **después de que tu solicitud sea aceptada**."; @@ -6310,7 +6924,10 @@ server test failure */ "You will still receive calls and notifications from muted profiles when they are active." = "Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos."; /* No comment provided by engineer. */ -"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes del chat. El historial del chat se conserva."; +"You will stop receiving messages from this channel. Chat history will be preserved." = "Dejarás de recibir mensajes de este canal. El historial del chat se conservará."; + +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes del chat. El historial del chat se conservará."; /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Dejarás de recibir mensajes del grupo. El historial del chat se conservará."; @@ -6333,6 +6950,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Llamadas"; +/* No comment provided by engineer. */ +"Your channel" = "Tu canal"; + /* No comment provided by engineer. */ "Your chat database" = "Base de datos"; @@ -6363,6 +6983,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Tus contactos permanecerán conectados."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Tus conversaciones te pertenecen, tal como ha sido siempre antes de la llegada de internet. Tu red no es un lugar que visitas. Es un lugar que has creado, te pertenece y nadie te la podrá quitar, ya sea pública o privada."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Tus credenciales podrían ser enviadas sin cifrar."; @@ -6378,6 +7001,9 @@ server test failure */ /* No comment provided by engineer. */ "Your ICE servers" = "Servidores ICE"; +/* No comment provided by engineer. */ +"Your network" = "Tu red"; + /* No comment provided by engineer. */ "Your preferences" = "Mis preferencias"; @@ -6387,6 +7013,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Tu perfil"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "El perfil **%@** será compartido con los servidores de canal y los suscriptores.\nLos servidores tienen acceso a los mensajes del canal."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "El perfil **%@** será compartido."; @@ -6399,9 +7028,18 @@ server test failure */ /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos."; +/* No comment provided by engineer. */ +"Your public address" = "Tu dirección pública"; + /* No comment provided by engineer. */ "Your random profile" = "Tu perfil aleatorio"; +/* No comment provided by engineer. */ +"Your relay address" = "Tu dirección de servidor"; + +/* No comment provided by engineer. */ +"Your relay name" = "Tu nombre del servidor"; + /* No comment provided by engineer. */ "Your server address" = "Dirección del servidor"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 5f81f94fb8..b75323054a 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -13,15 +13,9 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 värillinen!"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Osallistu](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Lähetä meille sähköpostia](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e-salattu** äänipuhelu"; @@ -257,9 +251,6 @@ swipe action */ /* call status */ "accepted call" = "hyväksytty puhelu"; -/* 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." = "Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi."; - /* No comment provided by engineer. */ "Add profile" = "Lisää profiili"; @@ -386,9 +377,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Vastaa puheluun"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; - /* No comment provided by engineer. */ "App build: %@" = "Sovellusversio: %@"; @@ -699,7 +687,7 @@ server test step */ /* alert title */ "Connection error" = "Yhteysvirhe"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Yhteysvirhe (AUTH)"; /* chat list item title (it should not be shown */ @@ -747,15 +735,15 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Jatka"; +/* No comment provided by engineer. */ +"Contribute" = "Osallistu"; + /* No comment provided by engineer. */ "Copy" = "Kopioi"; /* No comment provided by engineer. */ "Core version: v%@" = "Ydinversio: v%@"; -/* No comment provided by engineer. */ -"Create" = "Luo"; - /* server test step */ "Create file" = "Luo tiedosto"; @@ -861,9 +849,6 @@ server test step */ /* time unit */ "days" = "päivää"; -/* No comment provided by engineer. */ -"Decentralized" = "Hajautettu"; - /* message decrypt error item */ "Decryption error" = "Salauksen purkuvirhe"; @@ -1098,7 +1083,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "Muokkaa ryhmäprofiilia"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Salli"; /* No comment provided by engineer. */ @@ -1116,9 +1101,6 @@ alert button */ /* No comment provided by engineer. */ "Enable lock" = "Ota lukitus käyttöön"; -/* No comment provided by engineer. */ -"Enable notifications" = "Salli ilmoitukset"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Salli säännölliset ilmoitukset?"; @@ -1227,7 +1209,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "virhe"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Virhe"; /* No comment provided by engineer. */ @@ -1497,7 +1479,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Ryhmäkutsu ei ole enää voimassa, lähettäjä poisti sen."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Ryhmälinkki"; /* No comment provided by engineer. */ @@ -1599,9 +1581,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "Heti"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immuuni roskapostille ja väärinkäytöksille"; - /* No comment provided by engineer. */ "Import" = "Tuo"; @@ -1666,7 +1645,7 @@ server test error */ "Initial role" = "Alkuperäinen rooli"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Asenna SimpleX Chat terminaalille"; /* No comment provided by engineer. */ "Instant" = "Heti"; @@ -1683,7 +1662,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "virheelliset keskustelu-tiedot"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Virheellinen yhteyslinkki"; /* invalid chat item */ @@ -2064,9 +2043,6 @@ server test error */ /* copied message info in history */ "no text" = "ei tekstiä"; -/* No comment provided by engineer. */ -"No user identifiers." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; - /* No comment provided by engineer. */ "Notifications" = "Ilmoitukset"; @@ -2258,9 +2234,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "Yksityisyys ja turvallisuus"; -/* No comment provided by engineer. */ -"Privacy redefined" = "Yksityisyys uudelleen määritettynä"; - /* No comment provided by engineer. */ "Private filenames" = "Yksityiset tiedostonimet"; @@ -2273,9 +2246,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "Profiilin salasana"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profiilipäivitys lähetetään kontakteillesi."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Estä ääni- ja videopuhelut."; @@ -2328,13 +2298,10 @@ new chat action */ "Read more" = "Lue lisää"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in our GitHub repository." = "Lue lisää GitHub-arkistosta."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Lue lisää Käyttöoppaasta."; /* No comment provided by engineer. */ "Receipts are disabled" = "Kuittaukset pois käytöstä"; @@ -2697,15 +2664,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "Jaa osoite"; -/* alert title */ -"Share address with contacts?" = "Jaa osoite kontakteille?"; - /* No comment provided by engineer. */ "Share link" = "Jaa linkki"; -/* No comment provided by engineer. */ -"Share with contacts" = "Jaa kontaktien kanssa"; - /* No comment provided by engineer. */ "Show calls in phone history" = "Näytä puhelut puhelinhistoriassa"; @@ -2772,6 +2733,9 @@ chat item action */ /* notification title */ "Somebody" = "Joku"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Tähti GitHubissa"; + /* No comment provided by engineer. */ "Start chat" = "Aloita keskustelu"; @@ -2890,9 +2854,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Seuraavan sukupolven yksityisviestit"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Edellisen viestin tarkiste on erilainen."; @@ -3058,9 +3019,6 @@ server test failure */ /* No comment provided by engineer. */ "Use .onion hosts" = "Käytä .onion-isäntiä"; -/* No comment provided by engineer. */ -"Use chat" = "Käytä chattia"; - /* new chat action */ "Use current profile" = "Käytä nykyistä profiilia"; @@ -3265,9 +3223,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "Sinun on annettava tunnuslause aina, kun sovellus käynnistyy - sitä ei tallenneta laitteeseen."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 30f28c6b8c..329490f34b 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(cet appareil v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuer](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Contact par mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star sur GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation."; @@ -392,9 +386,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Connections actives"; -/* 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." = "Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts."; - /* No comment provided by engineer. */ "Add friends" = "Ajouter des amis"; @@ -638,9 +629,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Répondre à l'appel"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "N'importe qui peut heberger un serveur."; - /* No comment provided by engineer. */ "App build: %@" = "Build de l'app : %@"; @@ -864,7 +852,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgare, finnois, thaïlandais et ukrainien - grâce aux utilisateurs et à [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat) !"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Adresse professionnelle"; /* No comment provided by engineer. */ @@ -876,9 +864,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "En utilisant SimpleX Chat, vous acceptez de :\n- n'envoyer que du contenu légal dans les groupes publics.\n- respecter les autres utilisateurs - pas de spam."; - /* No comment provided by engineer. */ "call" = "appeler"; @@ -1158,9 +1143,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Configurer les serveurs ICE"; -/* No comment provided by engineer. */ -"Configure server operators" = "Configurer les opérateurs de serveur"; - /* No comment provided by engineer. */ "Confirm" = "Confirmer"; @@ -1291,7 +1273,7 @@ server test step */ /* alert title */ "Connection error" = "Erreur de connexion"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Erreur de connexion (AUTH)"; /* chat list item title (it should not be shown */ @@ -1378,6 +1360,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Continuer"; +/* No comment provided by engineer. */ +"Contribute" = "Contribuer"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Conversation supprimée !"; @@ -1396,9 +1381,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Corriger le nom pour %@ ?"; -/* No comment provided by engineer. */ -"Create" = "Créer"; - /* No comment provided by engineer. */ "Create 1-time link" = "Créer un lien unique"; @@ -1549,9 +1531,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Livraison de débogage"; -/* No comment provided by engineer. */ -"Decentralized" = "Décentralisé"; - /* message decrypt error item */ "Decryption error" = "Erreur de déchiffrement"; @@ -1937,7 +1916,7 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Modifier le profil du groupe"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Activer"; /* No comment provided by engineer. */ @@ -1964,9 +1943,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "Activer le verrouillage"; -/* No comment provided by engineer. */ -"Enable notifications" = "Activer les notifications"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Activer les notifications périodiques ?"; @@ -2108,7 +2084,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "erreur"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Erreur"; /* No comment provided by engineer. */ @@ -2610,7 +2586,7 @@ servers warning */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "L'invitation du groupe n'est plus valide, elle a été supprimé par l'expéditeur."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Lien du groupe"; /* No comment provided by engineer. */ @@ -2724,9 +2700,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Immédiatement"; -/* No comment provided by engineer. */ -"Immune to spam" = "Protégé du spam et des abus"; - /* No comment provided by engineer. */ "Import" = "Importer"; @@ -2821,7 +2794,7 @@ servers warning */ "Initial role" = "Rôle initial"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installer SimpleX Chat pour terminal"; /* No comment provided by engineer. */ "Instant" = "Instantané"; @@ -2841,7 +2814,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "données de chat invalides"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Lien de connection invalide"; /* invalid chat item */ @@ -3225,9 +3198,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Transférer l'appareil"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Transférer depuis un autre appareil"; - /* No comment provided by engineer. */ "Migrate here" = "Transférer ici"; @@ -3462,9 +3432,6 @@ servers warning */ /* copied message info in history */ "no text" = "aucun texte"; -/* No comment provided by engineer. */ -"No user identifiers." = "Aucun identifiant d'utilisateur."; - /* No comment provided by engineer. */ "Not compatible!" = "Non compatible !"; @@ -3509,7 +3476,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3578,7 +3545,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Seul votre contact peut envoyer des messages vocaux."; -/* alert action */ +/* alert action +alert button */ "Open" = "Ouvrir"; /* No comment provided by engineer. */ @@ -3779,9 +3747,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy for your customers." = "Respect de la vie privée de vos clients."; -/* No comment provided by engineer. */ -"Privacy redefined" = "La vie privée redéfinie"; - /* No comment provided by engineer. */ "Private filenames" = "Noms de fichiers privés"; @@ -3815,9 +3780,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Thème de profil"; -/* alert message */ -"Profile update will be sent to your contacts." = "La mise à jour du profil sera envoyée à vos contacts."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Interdire les appels audio/vidéo."; @@ -3900,16 +3862,10 @@ new chat action */ "Read more" = "En savoir plus"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Pour en savoir plus, consultez notre dépôt GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](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)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Pour en savoir plus, consultez le Guide de l'utilisateur."; /* No comment provided by engineer. */ "Receipts are disabled" = "Les accusés de réception sont désactivés"; @@ -4515,9 +4471,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Partager publiquement votre adresse"; -/* alert title */ -"Share address with contacts?" = "Partager l'adresse avec vos contacts ?"; - /* No comment provided by engineer. */ "Share from other apps." = "Partager depuis d'autres applications."; @@ -4536,9 +4489,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Partager sur SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Partager avec vos contacts"; - /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Afficher → sur les messages envoyés via le routage privé."; @@ -4674,6 +4624,9 @@ chat item action */ /* chat item text */ "standard end-to-end encryption" = "chiffrement de bout en bout standard"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Star sur GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Démarrer le chat"; @@ -4767,9 +4720,6 @@ chat item action */ /* No comment provided by engineer. */ "Tap button " = "Appuyez sur le bouton "; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Appuyez sur Créer une adresse SimpleX dans le menu pour la créer ultérieurement."; - /* No comment provided by engineer. */ "Tap to activate profile." = "Appuyez pour activer un profil."; @@ -4858,9 +4808,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion !"; -/* No comment provided by engineer. */ -"The future of messaging" = "La nouvelle génération de messagerie privée"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Le hash du message précédent est différent."; @@ -5011,9 +4958,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Afficher la liste des conversations :"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Basculer en mode incognito lors de la connexion."; @@ -5179,9 +5123,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Utiliser %@"; -/* No comment provided by engineer. */ -"Use chat" = "Utiliser le chat"; - /* new chat action */ "Use current profile" = "Utiliser le profil actuel"; @@ -5569,9 +5510,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Vous choisissez qui peut se connecter."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Vous avez déjà demandé une connexion !\nRépéter la demande de connexion ?"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 93eb633dd7..623d79433c 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -10,6 +10,9 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilabb üzenetkézbesítés.\n- picit továbbfejlesztett csoportok.\n- és még sok más!"; +/* No comment provided by engineer. */ +"- opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking." = "- Hivatkozások előnézetének küldése.\n- Hiperhivatkozásokon keresztüli adathalászat megakadályozása.\n- Hivatkozások nyomonkövetési paramétereinek eltávolítása."; + /* No comment provided by engineer. */ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- partnerek értesítése a törlésről (nem kötelező)\n- profilnevek szóközökkel\n- és még sok más!"; @@ -19,21 +22,21 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 színezett!"; +/* chat link info line */ +"(from owner)" = "(a tulajdonostól)"; + /* No comment provided by engineer. */ "(new)" = "(új)"; +/* chat link info line */ +"(signed)" = "(aláírva)"; + /* No comment provided by engineer. */ "(this device v%@)" = "(ez az eszköz: v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Közreműködés](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Küldjön nekünk e-mailt](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Partner hozzáadása:** új meghívási hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; @@ -50,13 +53,13 @@ "**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." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van."; /* No comment provided by engineer. */ -"**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." = "**Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; +"**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." = "**A legprivátabb**: Az alkalmazás nem használja a SimpleX Chat push-kiszolgálóját. Az alkalmazás a háttérben ellenőrzi az üzeneteket, amikor a rendszer ezt lehetővé teszi, attól függően, hogy Ön milyen gyakran használja az alkalmazást."; /* 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." = "**Megjegyzés:** ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja a partnereitől érkező üzenetek visszafejtését."; /* No comment provided by engineer. */ -"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Megjegyzés:** NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti."; +"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Megjegyzés:** NEM fogja tudni helyreállítani vagy módosítani a jelmondatot abban az esetben, ha elveszíti."; /* No comment provided by engineer. */ "**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." = "**Megjegyzés:** az eszköztoken és az értesítések el lesznek küldve a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; @@ -64,6 +67,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Hivatkozás beolvasása / beillesztése**: egy kapott hivatkozáson keresztüli kapcsolódáshoz."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Átjátszó tesztelése** a nevének lekéréséhez."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali leküldéses értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; @@ -175,6 +181,18 @@ /* time interval */ "%d months" = "%d hónap"; +/* channel relay bar +channel subscriber relay bar */ +"%d relays failed" = "%d átjátszóhoz nem sikerült kapcsolódni"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays not active" = "%d átjátszó inaktív"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays removed" = "%d átjátszó eltávolítva"; + /* time interval */ "%d sec" = "%d mp"; @@ -184,15 +202,50 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d üzenet kihagyva"; +/* channel subscriber count */ +"%d subscriber" = "%d feliratkozó"; + +/* channel subscriber count */ +"%d subscribers" = "%d feliratkozó"; + /* time interval */ "%d weeks" = "%d hét"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d átjátszó aktív"; + +/* channel relay bar */ +"%d/%d relays active, %d errors" = "%1$d/%2$d átjátszó aktív, %3$d hiba"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d átjátszó aktív, %3$d sikertelen"; + +/* channel relay bar */ +"%d/%d relays active, %d removed" = "%1$d/%2$d átjátszó aktív, %3$d eltávolítva"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d átjátszó kapcsolódva"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d átjátszó kapcsolódva, %3$d hiba"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d failed" = "%1$d/%2$d átjátszó kapcsolódott, %3$d átjátszóhoz nem sikerült kapcsolódni"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d removed" = "%1$d/%2$d átjátszó kapcsolódott, %3$d eltávolítva"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld csatornaesemény"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld partner kiválasztva"; @@ -262,6 +315,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~áthúzott~"; +/* owner verification */ +"⚠️ Signature verification failed: %@." = "⚠️ Nem sikerült ellenőrizni az aláírást: %@."; + /* time to disappear */ "0 sec" = "0 mp"; @@ -293,7 +349,7 @@ time interval */ "1-time link" = "Egyszer használható meghívó"; /* No comment provided by engineer. */ -"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható."; +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó alkalmazáson keresztül megosztható."; /* No comment provided by engineer. */ "5 minutes" = "5 perc"; @@ -307,6 +363,9 @@ time interval */ /* No comment provided by engineer. */ "A few more things" = "Néhány további dolog"; +/* No comment provided by engineer. */ +"A link for one person to connect" = "Egy hivatkozás, ami egyetlen partnerrel való kapcsolat létrehozására szolgál"; + /* notification title */ "A new contact" = "Egy új partner"; @@ -371,6 +430,9 @@ swipe action */ /* alert title */ "Accept member" = "Tag befogadása"; +/* No comment provided by engineer. */ +"accepted" = "elfogadva"; + /* rcv group event chat item */ "accepted %@" = "befogadta őt: %@"; @@ -392,6 +454,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Visszaigazolási hibák"; +/* No comment provided by engineer. */ +"active" = "aktív"; + /* token status text */ "Active" = "Aktív"; @@ -399,7 +464,7 @@ swipe action */ "Active connections" = "Aktív kapcsolatok száma"; /* 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." = "Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára."; +"Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts." = "Cím hozzáadása a profilhoz, hogy a SimpleX partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve a SimpleX partnerei számára."; /* No comment provided by engineer. */ "Add friends" = "Barátok hozzáadása"; @@ -440,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "Added message servers" = "Hozzáadott üzenetkiszolgálók"; +/* No comment provided by engineer. */ +"Adding relays will be supported later." = "Az átjátszók hozzáadása később lesz támogatott."; + /* No comment provided by engineer. */ "Additional accent" = "További kiemelőszín"; @@ -530,6 +598,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Összes profil"; +/* No comment provided by engineer. */ +"All relays failed" = "Nem sikerült kapcsolódni egyetlen átjátszóhoz sem"; + +/* No comment provided by engineer. */ +"All relays removed" = "Az összes átjátszó el lett távolítva"; + /* No comment provided by engineer. */ "All reports will be archived for you." = "Az összes jelentés archiválva lesz az Ön számára."; @@ -566,6 +640,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra)"; +/* No comment provided by engineer. */ +"Allow members to chat with admins." = "A csevegés az adminisztrátorokkal engedélyezve van a tagok számára."; + /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "A reakciók hozzáadása az üzenetekhez csak abban az esetben van engedélyezve, ha a partnere is engedélyezi."; @@ -575,12 +652,18 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között engedélyezve van."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "A közvetlen üzenetek küldése a feliratkozók között engedélyezve van."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Az eltűnő üzenetek küldése engedélyezve van."; /* No comment provided by engineer. */ "Allow sharing" = "Megosztás engedélyezése"; +/* No comment provided by engineer. */ +"Allow subscribers to chat with admins." = "A csevegés az adminisztrátorokkal engedélyezve van a feliratkozók számára."; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Az elküldött üzenetek végleges törlése engedélyezve van. (24 óra)"; @@ -650,9 +733,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Hívás fogadása"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Bárki üzemeltethet kiszolgálókat."; - /* No comment provided by engineer. */ "App build: %@" = "Alkalmazás összeállítási száma: %@"; @@ -794,6 +874,15 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Hibás az üzenet azonosítója"; +/* No comment provided by engineer. */ +"Be free\nin your network" = "Váljon szabaddá\na saját hálózatában"; + +/* No comment provided by engineer. */ +"Be free in your network." = "Legyen szabad a saját hálózatában."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe."; + /* No comment provided by engineer. */ "Better calls" = "Továbbfejlesztett hívásélmény"; @@ -851,6 +940,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Letiltja a tagot?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Az összes feliratkozó számára letiltja a feliratkozót?"; + /* marked deleted chat item preview text */ "blocked" = "letiltva"; @@ -895,9 +987,15 @@ marked deleted chat item preview text */ "Both you and your contact can send voice messages." = "Mindkét fél küldhet hangüzeneteket."; /* No comment provided by engineer. */ -"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +"Bottom bar" = "Alsó sáv"; + +/* compose placeholder for channel owner */ +"Broadcast" = "Közvetítés…"; /* No comment provided by engineer. */ +"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* chat link info line */ "Business address" = "Üzleti cím"; /* No comment provided by engineer. */ @@ -912,9 +1010,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek."; - /* No comment provided by engineer. */ "call" = "hívás"; @@ -939,6 +1034,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Camera not available" = "A kamera nem elérhető"; +/* No comment provided by engineer. */ +"can't broadcast" = "nem lehet közvetíteni"; + /* No comment provided by engineer. */ "Can't call contact" = "Nem lehet felhívni a partnert"; @@ -1038,6 +1136,58 @@ set passcode view */ /* chat item text */ "changing address…" = "cím módosítása…"; +/* shown as sender role for channel messages */ +"channel" = "csatorna"; + +/* No comment provided by engineer. */ +"Channel" = "Csatorna"; + +/* No comment provided by engineer. */ +"Channel display name" = "Csatorna megjelenítendő neve"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Csatorna teljes neve (nem kötelező)"; + +/* alert message +alert subtitle */ +"Channel has no active relays. Please try to join later." = "A csatornának nincsenek aktív átjátszói. Próbáljon meg később csatlakozni."; + +/* No comment provided by engineer. */ +"Channel image" = "Csatornakép"; + +/* chat link info line */ +"Channel link" = "Csatornahivatkozás"; + +/* No comment provided by engineer. */ +"Channel preferences" = "Csatornabeállítások"; + +/* No comment provided by engineer. */ +"Channel profile" = "Csatornaprofil"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "A csatornaprofil a feliratkozók eszközén és a csevegési átjátszókon van tárolva."; + +/* snd group event chat item */ +"channel profile updated" = "csatornaprofil frissítve"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "A csatornaprofil módosult. Ha menti, akkor a frissített profil el lesz küldve a csatorna feliratkozóinak."; + +/* alert title */ +"Channel temporarily unavailable" = "A csatorna ideiglenesen nem érhető el"; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "A csatorna az összes feliratkozó számára törölve lesz – ez a művelet nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "A csatorna törölve lesz az Ön számára – ez a művelet nem vonható vissza!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "A csatorna %2$d átjátszóból %1$d használatával kezd el működni. Folytatja?"; + +/* No comment provided by engineer. */ +"Channels" = "Csatornák"; + /* No comment provided by engineer. */ "Chat" = "Csevegés"; @@ -1089,6 +1239,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Csevegési profil"; +/* No comment provided by engineer. */ +"Chat relay" = "Csevegési átjátszó"; + +/* No comment provided by engineer. */ +"Chat relays" = "Csevegési átjátszók"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "A csevegési átjátszók továbbítják az üzeneteket az Ön által létrehozott csatornákban."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "A csevegési átjátszók továbbítják az üzeneteket a csatorna feliratkozóinak."; + /* No comment provided by engineer. */ "Chat theme" = "Csevegés témája"; @@ -1098,7 +1260,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "A csevegés törölve lesz az Ön számára – ez a művelet nem vonható vissza!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Csevegés az adminisztrátorokkal"; /* No comment provided by engineer. */ @@ -1110,15 +1273,30 @@ set passcode view */ /* No comment provided by engineer. */ "Chats" = "Csevegések"; +/* No comment provided by engineer. */ +"Chats with admins are prohibited." = "A csevegés az adminisztrátorokkal le van tiltva."; + +/* alert message */ +"Chats with admins in public channels have no E2E encryption - use only with trusted chat relays." = "A nyilvános csatornákban az adminisztrátorokkal való csevegések nem rendelkeznek végpontok közötti titkosítással – csak megbízható csevegési átjátszókkal használja őket."; + /* No comment provided by engineer. */ "Chats with members" = "Csevegés a tagokkal"; +/* No comment provided by engineer. */ +"Chats with members are disabled" = "A csevegés a tagokkal le van tiltva"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Üzenetek ellenőrzése 20 percenként."; /* No comment provided by engineer. */ "Check messages when allowed." = "Üzenetek ellenőrzése, amikor engedélyezett."; +/* alert message */ +"Check relay address and try again." = "Ellenőrizze az átjátszó címét, és próbálja újra."; + +/* alert message */ +"Check relay name and try again." = "Ellenőrizze az átjátszó nevét, és próbálja újra."; + /* alert title */ "Check server address and try again." = "Kiszolgáló címének ellenőrzése és újrapróbálkozás."; @@ -1213,7 +1391,7 @@ set passcode view */ "Configure ICE servers" = "ICE-kiszolgálók beállítása"; /* No comment provided by engineer. */ -"Configure server operators" = "Kiszolgálóüzemeltetők beállítása"; +"Configure relays" = "Átjátszók konfigurálása"; /* No comment provided by engineer. */ "Confirm" = "Megerősítés"; @@ -1279,6 +1457,9 @@ server test step */ /* new chat sheet title */ "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; +/* No comment provided by engineer. */ +"Connect via link or QR code" = "Hivatkozás vagy QR-kód használata"; + /* new chat sheet title */ "Connect via one-time link" = "Kapcsolódás az egyszer használható meghívón keresztül"; @@ -1348,7 +1529,7 @@ server test step */ /* alert title */ "Connection error" = "Kapcsolódási hiba"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Kapcsolódási hiba (AUTH)"; /* chat list item title (it should not be shown */ @@ -1393,6 +1574,9 @@ server test step */ /* profile update event chat item */ "contact %@ changed to %@" = "%1$@ a következőre módosította a nevét: %2$@"; +/* chat link info line */ +"Contact address" = "Kapcsolattartási cím"; + /* No comment provided by engineer. */ "Contact allows" = "Partner engedélyezi"; @@ -1453,6 +1637,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Folytatás"; +/* No comment provided by engineer. */ +"Contribute" = "Közreműködés"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Beszélgetés törölve!"; @@ -1460,7 +1647,7 @@ server test step */ "Copy" = "Másolás"; /* No comment provided by engineer. */ -"Copy error" = "Másolási hiba"; +"Copy error" = "Hiba másolása"; /* No comment provided by engineer. */ "Core version: v%@" = "Fő verzió: v%@"; @@ -1471,9 +1658,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Helyesbíti a nevet a következőre: %@?"; -/* No comment provided by engineer. */ -"Create" = "Létrehozás"; - /* No comment provided by engineer. */ "Create 1-time link" = "Egyszer használható meghívó létrehozása"; @@ -1501,6 +1685,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Profil létrehozása"; +/* No comment provided by engineer. */ +"Create public channel" = "Nyilvános csatorna létrehozása"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Nyilvános csatorna létrehozása (BÉTA)"; + /* server test step */ "Create queue" = "Várólista létrehozása"; @@ -1510,9 +1700,15 @@ server test step */ /* No comment provided by engineer. */ "Create your address" = "Saját cím létrehozása"; +/* No comment provided by engineer. */ +"Create your link" = "Saját hivatkozás létrehozása"; + /* No comment provided by engineer. */ "Create your profile" = "Profil létrehozása"; +/* No comment provided by engineer. */ +"Create your public address" = "Saját nyilvános cím létrehozása"; + /* No comment provided by engineer. */ "Created" = "Létrehozva"; @@ -1525,6 +1721,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Archívum hivatkozás létrehozása"; +/* No comment provided by engineer. */ +"Creating channel" = "Csatorna létrehozása"; + /* No comment provided by engineer. */ "Creating link…" = "Hivatkozás létrehozása…"; @@ -1627,8 +1826,8 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Kézbesítési hibák felderítése"; -/* No comment provided by engineer. */ -"Decentralized" = "Decentralizált"; +/* relay test step */ +"Decode link" = "Hivatkozás dekódolása"; /* message decrypt error item */ "Decryption error" = "Titkosítás-visszafejtési hiba"; @@ -1671,6 +1870,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Törlés, és a partner értesítése"; +/* No comment provided by engineer. */ +"Delete channel" = "Csatorna törlése"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Törli a csatornát?"; + /* No comment provided by engineer. */ "Delete chat" = "Csevegés törlése"; @@ -1774,6 +1979,9 @@ alert button */ /* server test step */ "Delete queue" = "Várólista törlése"; +/* No comment provided by engineer. */ +"Delete relay" = "Átjátszó törlése"; + /* No comment provided by engineer. */ "Delete report" = "Jelentés törlése"; @@ -1798,6 +2006,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Törölve: %@"; +/* rcv group event chat item */ +"deleted channel" = "törölt csatorna"; + /* rcv direct event chat item */ "deleted contact" = "törölt partner"; @@ -1888,6 +2099,12 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "A tagok közötti közvetlen üzenetek le vannak tiltva."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "A feliratkozók közötti közvetlen üzenetek le vannak tiltva."; + +/* alert button */ +"Disable" = "Letiltás"; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Letiltás (egyéni beállítások megtartása)"; @@ -1945,6 +2162,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Az előzmények ne legyenek elküldve az új tagok számára."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Az előzmények ne legyenek elküldve az új feliratkozók számára."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; @@ -2024,27 +2244,39 @@ chat item action */ /* No comment provided by engineer. */ "E2E encrypted notifications." = "Végpontok között titkosított értesítések."; +/* No comment provided by engineer. */ +"Easier to invite your friends 👋" = "Könnyebben hívhatja meg a barátait 👋"; + /* chat item action */ "Edit" = "Szerkesztés"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Csatornaprofil szerkesztése"; + /* No comment provided by engineer. */ "Edit group profile" = "Csoportprofil szerkesztése"; /* No comment provided by engineer. */ "Empty message!" = "Üres üzenet!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Engedélyezés"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Engedélyezés (egyéni beállítások megtartása)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Engedélyezzen legalább egy csevegési átjátszót a „Hálózat és kiszolgálók” menüben."; + /* alert title */ "Enable automatic message deletion?" = "Engedélyezi az automatikus üzenettörlést?"; /* No comment provided by engineer. */ "Enable camera access" = "Kamera-hozzáférés engedélyezése"; +/* alert title */ +"Enable chats with admins?" = "Engedélyezi a csevegést az adminisztrátorokkal?"; + /* No comment provided by engineer. */ "Enable disappearing messages by default." = "Eltűnő üzenetek engedélyezése alapértelmezetten."; @@ -2060,11 +2292,11 @@ chat item action */ /* No comment provided by engineer. */ "Enable instant notifications?" = "Engedélyezi az azonnali értesítéseket?"; -/* No comment provided by engineer. */ -"Enable lock" = "Zárolás engedélyezése"; +/* alert title */ +"Enable link previews?" = "Engedélyezi a hivatkozások előnézetét?"; /* No comment provided by engineer. */ -"Enable notifications" = "Értesítések engedélyezése"; +"Enable lock" = "Zárolás engedélyezése"; /* No comment provided by engineer. */ "Enable periodic notifications?" = "Engedélyezi az időszakos értesítéseket?"; @@ -2171,6 +2403,9 @@ chat item action */ /* call status */ "ended call %@" = "%@ hívása véget ért"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Adja meg a csatorna nevét…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Adja meg a helyes jelmondatot."; @@ -2189,6 +2424,12 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Adja meg a jelszót fentebb a megjelenítéshez!"; +/* No comment provided by engineer. */ +"Enter profile name..." = "Profil nevének megadása…"; + +/* No comment provided by engineer. */ +"Enter relay name…" = "Adja meg az átjátszó nevét…"; + /* No comment provided by engineer. */ "Enter server manually" = "Kiszolgáló megadása kézzel"; @@ -2207,7 +2448,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "hiba"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Hiba"; /* No comment provided by engineer. */ @@ -2225,6 +2466,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Hiba történt a tag(ok) hozzáadásakor"; +/* alert title */ +"Error adding relay" = "Hiba az átjátszó hozzáadásakor"; + /* alert title */ "Error adding server" = "Hiba történt a kiszolgáló hozzáadásakor"; @@ -2261,6 +2505,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Hiba történt a cím létrehozásakor"; +/* alert title */ +"Error creating channel" = "Hiba a csatorna létrehozásakor"; + /* No comment provided by engineer. */ "Error creating group" = "Hiba történt a csoport létrehozásakor"; @@ -2366,6 +2613,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Hiba történt a statisztikák visszaállításakor"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Hiba a csatornaprofil mentésekor"; + /* alert title */ "Error saving chat list" = "Hiba történt a csevegési lista mentésekor"; @@ -2408,6 +2658,9 @@ chat item action */ /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Hiba történt a kézbesítési jelentések beállításakor!"; +/* alert title */ +"Error sharing channel" = "Hiba a csatorna megosztásakor"; + /* No comment provided by engineer. */ "Error starting chat" = "Hiba történt a csevegés elindításakor"; @@ -2450,6 +2703,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Hiba: "; +/* receive error chat item */ +"error: %@" = "hiba: %@"; + /* alert message file error text snd error text */ @@ -2634,6 +2890,9 @@ server test error */ /* No comment provided by engineer. */ "For all moderators" = "Az összes moderátor számára"; +/* No comment provided by engineer. */ +"For anyone to reach you" = "Bárki számára, aki el szeretné érni Önt"; + /* servers error servers warning */ "For chat profile %@:" = "A(z) %@ nevű csevegési profilhoz:"; @@ -2719,9 +2978,15 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Tovább csökkentett akkumulátor-használat"; +/* relay test step */ +"Get link" = "Hivatkozás megtekintése"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Kapjon értesítést, ha megemlítik."; +/* No comment provided by engineer. */ +"Get started" = "Vágjunk bele"; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIF-ek és matricák"; @@ -2767,7 +3032,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "csoport törölve"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Csoporthivatkozás"; /* No comment provided by engineer. */ @@ -2839,6 +3104,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "Az előzmények nem lesznek elküldve az új tagok számára."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "Az előzmények nem lesznek elküldve az új feliratkozók számára."; + /* time unit */ "hours" = "óra"; @@ -2899,9 +3167,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Azonnal"; -/* No comment provided by engineer. */ -"Immune to spam" = "Védett a kéretlen tartalommal szemben"; - /* No comment provided by engineer. */ "Import" = "Importálás"; @@ -3002,7 +3267,7 @@ servers warning */ "Initial role" = "Kezdeti szerepkör"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "A SimpleX Chat terminálhoz telepítése"; /* No comment provided by engineer. */ "Instant" = "Azonnali"; @@ -3037,7 +3302,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "érvénytelen csevegésadat"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Érvénytelen kapcsolattartási hivatkozás"; /* invalid chat item */ @@ -3058,6 +3323,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Érvénytelen QR-kód"; +/* alert title */ +"Invalid relay address!" = "Érvénytelen az átjátszó címe!"; + +/* alert title */ +"Invalid relay name!" = "Érvénytelen az átjátszó neve!"; + /* No comment provided by engineer. */ "Invalid response" = "Érvénytelen válasz"; @@ -3085,6 +3356,9 @@ servers warning */ /* No comment provided by engineer. */ "Invite members" = "Tagok meghívása"; +/* No comment provided by engineer. */ +"Invite someone privately" = "Partner meghívása privátban"; + /* No comment provided by engineer. */ "Invite to chat" = "Meghívás a csevegésbe"; @@ -3151,6 +3425,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "Csatlakozás mint: %@"; +/* No comment provided by engineer. */ +"Join channel" = "Csatlakozás a csatornához"; + /* new chat sheet title */ "Join group" = "Csatlakozás a csoporthoz"; @@ -3199,6 +3476,12 @@ servers warning */ /* swipe action */ "Leave" = "Elhagyás"; +/* No comment provided by engineer. */ +"Leave channel" = "Csatorna elhagyása"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Elhagyja a csatornát?"; + /* No comment provided by engineer. */ "Leave chat" = "Csevegés elhagyása"; @@ -3217,6 +3500,9 @@ servers warning */ /* No comment provided by engineer. */ "Less traffic on mobile networks." = "Kevesebb adatforgalom a mobilhálózatokon."; +/* No comment provided by engineer. */ +"Let someone connect to you" = "Hagyja, hogy valaki elérje Önt"; + /* email subject */ "Let's talk in SimpleX Chat" = "Beszélgessünk a SimpleX Chatben"; @@ -3226,9 +3512,15 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Korlátozások"; +/* No comment provided by engineer. */ +"link" = "hivatkozás"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗"; +/* owner verification */ +"Link signature verified." = "Hivatkozás aláírása ellenőrizve."; + /* No comment provided by engineer. */ "Linked desktop options" = "Társított számítógép beállítások"; @@ -3358,6 +3650,9 @@ servers warning */ /* No comment provided by engineer. */ "Members can add message reactions." = "A tagok reakciókat adhatnak hozzá az üzenetekhez."; +/* No comment provided by engineer. */ +"Members can chat with admins." = "A tagok cseveghetnek az adminisztrátorokkal"; + /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; @@ -3400,6 +3695,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Piszkozatok"; +/* No comment provided by engineer. */ +"Message error" = "Üzenethiba"; + /* item status text */ "Message forwarded" = "Továbbított üzenet"; @@ -3460,6 +3758,12 @@ servers warning */ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "%@ összes üzenete meg fog jelenni!"; +/* No comment provided by engineer. */ +"Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages." = "Ebben a csatornában az üzenetek **nem rendelkeznek végpontok közötti titkosítással**. A csevegési átjátszók láthatják ezeket az üzeneteket."; + +/* E2EE info chat item */ +"Messages in this channel are not end-to-end encrypted. Chat relays can see these messages." = "Ebben a csatornában az üzenetek nem rendelkeznek végpontok közötti titkosítással. A csevegési átjátszók láthatják ezeket az üzeneteket."; + /* alert message */ "Messages in this chat will never be deleted." = "Az ebben a csevegésben lévő üzenetek soha nem lesznek törölve."; @@ -3470,7 +3774,7 @@ servers warning */ "Messages sent" = "Elküldött üzenetek"; /* alert message */ -"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kiváasztotta őket."; +"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kiválasztotta őket."; /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve."; @@ -3479,10 +3783,10 @@ servers warning */ "Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve."; /* No comment provided by engineer. */ -"Migrate device" = "Eszköz átköltöztetése"; +"Migrate" = "Átköltöztetés"; /* No comment provided by engineer. */ -"Migrate from another device" = "Átköltöztetés egy másik eszközről"; +"Migrate device" = "Eszköz átköltöztetése"; /* No comment provided by engineer. */ "Migrate here" = "Átköltöztetés ide"; @@ -3574,12 +3878,18 @@ servers warning */ /* No comment provided by engineer. */ "Network & servers" = "Hálózat és kiszolgálók"; +/* No comment provided by engineer. */ +"Network commitments" = "Hálózati kötelezettségvállalások"; + /* No comment provided by engineer. */ "Network connection" = "Hálózati kapcsolat"; /* No comment provided by engineer. */ "Network decentralization" = "Hálózati decentralizáció"; +/* conn error description */ +"Network error" = "Hálózati hiba"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Hálózati problémák – az üzenet többszöri elküldési kísérlet után lejárt."; @@ -3589,6 +3899,9 @@ servers warning */ /* No comment provided by engineer. */ "Network operator" = "Hálózatüzemeltető"; +/* No comment provided by engineer. */ +"Network routers cannot know\nwho talks to whom" = "A hálózati útválasztók nem tudhatják,\nhogy ki kivel beszélget"; + /* No comment provided by engineer. */ "Network settings" = "Hálózati beállítások"; @@ -3598,15 +3911,24 @@ servers warning */ /* delete after time */ "never" = "soha"; +/* No comment provided by engineer. */ +"new" = "új"; + /* token status text */ "New" = "Új"; +/* No comment provided by engineer. */ +"New 1-time link" = "Új egyszer használható meghívó"; + /* No comment provided by engineer. */ "New chat" = "Új csevegés"; /* No comment provided by engineer. */ "New chat experience 🎉" = "Új csevegési élmény 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Új csevegési átjátszó"; + /* notification */ "New contact request" = "Új partneri kapcsolatkérés"; @@ -3664,9 +3986,21 @@ servers warning */ /* No comment provided by engineer. */ "No" = "Nem"; +/* No comment provided by engineer. */ +"No account. No phone. No email. No ID.\nThe most secure encryption." = "Nincs fiók. Nincs telefonszám. Nincs e-mail-cím. Nincs személyazonosító.\nA legbiztonságosabb titkosítás."; + +/* No comment provided by engineer. */ +"No active relays" = "Nincsenek aktív átjátszók"; + /* Authentication unavailable */ "No app password" = "Nincs alkalmazás jelszó"; +/* No comment provided by engineer. */ +"No chat relays" = "Nincsenek csevegési átjátszók"; + +/* servers warning */ +"No chat relays enabled." = "Nincsenek engedélyezve csevegési átjátszók."; + /* No comment provided by engineer. */ "No chats" = "Nincsenek csevegések"; @@ -3764,7 +4098,16 @@ servers warning */ "No unread chats" = "Nincsenek olvasatlan csevegések"; /* No comment provided by engineer. */ -"No user identifiers." = "Nincsenek felhasználói azonosítók."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk."; + +/* No comment provided by engineer. */ +"Non-profit governance" = "Nonprofit irányítás"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide - Ön itt szuverén."; + +/* alert title */ +"Not all relays connected" = "Nem minden átjátszó kapcsolódott"; /* No comment provided by engineer. */ "Not compatible!" = "Nem kompatibilis!"; @@ -3822,7 +4165,7 @@ alert button new chat action */ "Ok" = "Rendben"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "Rendben"; /* No comment provided by engineer. */ @@ -3831,9 +4174,15 @@ new chat action */ /* group pref value */ "on" = "bekapcsolva"; +/* No comment provided by engineer. */ +"On your phone, not on servers." = "Az eszközön, nem pedig kiszolgálókon."; + /* No comment provided by engineer. */ "One-time invitation link" = "Egyszer használható meghívó"; +/* chat link info line */ +"One-time link" = "Egyszer használható meghívó"; + /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion kiszolgálók **szükségesek** a kapcsolódáshoz.\nKompatibilis VPN szükséges."; @@ -3843,6 +4192,9 @@ new chat action */ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Az onion kiszolgálók nem lesznek használva."; +/* No comment provided by engineer. */ +"Only channel owners can change channel preferences." = "Csak a csatorna tulajdonosai módosíthatják a csatornabeállításokat."; + /* No comment provided by engineer. */ "Only chat owners can change preferences." = "Csak a csevegés tulajdonosai módosíthatják a csevegési beállításokat."; @@ -3903,12 +4255,16 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Csak a partnere küldhet hangüzeneteket."; -/* alert action */ +/* alert action +alert button */ "Open" = "Megnyitás"; /* No comment provided by engineer. */ "Open changes" = "Módosítások megtekintése"; +/* new chat action */ +"Open channel" = "Csatorna megnyitása"; + /* new chat action */ "Open chat" = "Csevegés megnyitása"; @@ -3921,6 +4277,9 @@ new chat action */ /* No comment provided by engineer. */ "Open conditions" = "Feltételek megnyitása"; +/* alert title */ +"Open external link?" = "Megnyitja a külső hivatkozást?"; + /* alert action */ "Open full link" = "Teljes hivatkozás megnyitása"; @@ -3933,6 +4292,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Átköltöztetés indítása egy másik eszközre"; +/* new chat action */ +"Open new channel" = "Új csatorna megnyitása"; + /* new chat action */ "Open new chat" = "Új csevegés megnyitása"; @@ -3963,6 +4325,9 @@ new chat action */ /* alert title */ "Operator server" = "Kiszolgáló-üzemeltető"; +/* No comment provided by engineer. */ +"Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code" = "Az üzemeltetők kijelentik, hogy:\n- függetlenek maradnak\n- minimálisra csökkentik a metaadatok használatát\n- ellenőrzött, nyílt forráskódú szoftvereket futtatnak"; + /* No comment provided by engineer. */ "Or import archive file" = "Vagy archívumfájl importálása"; @@ -3975,12 +4340,18 @@ new chat action */ /* No comment provided by engineer. */ "Or securely share this file link" = "Vagy ossza meg biztonságosan ezt a fájlhivatkozást"; +/* No comment provided by engineer. */ +"Or show QR in person or via video call." = "Vagy mutassa meg a QR-kódot személyesen vagy videóhíváson keresztül."; + /* No comment provided by engineer. */ "Or show this code" = "Vagy mutassa meg ezt a kódot"; /* No comment provided by engineer. */ "Or to share privately" = "Vagy a privát megosztáshoz"; +/* No comment provided by engineer. */ +"Or use this QR - print or show online." = "Vagy használja ezt a QR-kódot – nyomtassa ki vagy mutassa meg online."; + /* No comment provided by engineer. */ "Organize chats into lists" = "Csevegések listákba szervezése"; @@ -3999,9 +4370,18 @@ new chat action */ /* member role */ "owner" = "tulajdonos"; +/* No comment provided by engineer. */ +"Owner" = "Tulajdonos"; + /* feature role */ "owners" = "tulajdonosok"; +/* No comment provided by engineer. */ +"Owners" = "Tulajdonosok"; + +/* No comment provided by engineer. */ +"Ownership: you can run your own relays." = "Tulajdonjog: saját átjátszókat üzemeltethet."; + /* No comment provided by engineer. */ "Passcode" = "Jelkód"; @@ -4029,6 +4409,9 @@ new chat action */ /* No comment provided by engineer. */ "Paste image" = "Kép beillesztése"; +/* No comment provided by engineer. */ +"Paste link / Scan" = "Hivatkozás megadása vagy QR-kód beolvasása"; + /* No comment provided by engineer. */ "Paste link to connect!" = "Hivatkozás beillesztése a kapcsolódáshoz!"; @@ -4137,6 +4520,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt."; +/* No comment provided by engineer. */ +"Preset relay address" = "Előre beállított átjátszó címe"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Előre beállított átjátszó neve"; + /* No comment provided by engineer. */ "Preset server address" = "Előre beállított kiszolgáló címe"; @@ -4159,10 +4548,10 @@ new chat action */ "Privacy policy and conditions of use." = "Adatvédelmi szabályzat és felhasználási feltételek."; /* No comment provided by engineer. */ -"Privacy redefined" = "Újraértelmezett adatvédelem"; +"Privacy: for owners and subscribers." = "Adatvédelem: tulajdonosok és előfizetők számára."; /* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára."; +"Private and secure messaging." = "Privát és biztonságos üzenetváltás."; /* No comment provided by engineer. */ "Private filenames" = "Privát fájlnevek"; @@ -4188,6 +4577,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Privát útválasztás időtúllépése"; +/* alert action */ +"Proceed" = "Folytatás"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil és kiszolgálókapcsolatok"; @@ -4204,11 +4596,14 @@ new chat action */ "Profile theme" = "Profiltéma"; /* alert message */ -"Profile update will be sent to your contacts." = "A profilfrissítés el lesz küldve a partnerei számára."; +"Profile update will be sent to your SimpleX contacts." = "A profilfrissítés el lesz küldve a SimpleX partnerei számára."; /* No comment provided by engineer. */ "Prohibit audio/video calls." = "A hívások kezdeményezése le van tiltva."; +/* No comment provided by engineer. */ +"Prohibit chats with admins." = "A csevegés az adminisztrátorokkal le van tiltva."; + /* No comment provided by engineer. */ "Prohibit irreversible message deletion." = "Az elküldött üzenetek végleges törlése le van tiltva."; @@ -4224,6 +4619,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között le van tiltva."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "A közvetlen üzenetek küldése a feliratkozók között le van tiltva."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldése le van tiltva."; @@ -4266,6 +4664,9 @@ new chat action */ /* No comment provided by engineer. */ "Proxy requires password" = "A proxy jelszót igényel"; +/* No comment provided by engineer. */ +"Public channels - speak freely 🚀" = "Nyilvános csatornák – mondja el szabadon a véleményét 🚀"; + /* No comment provided by engineer. */ "Push notifications" = "Leküldéses értesítések"; @@ -4294,16 +4695,10 @@ new chat action */ "Read more" = "Tudjon meg többet"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "További információ a GitHub-tárolónkban."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "További információ a [Használati útmutatóban](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)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "További információ a [GitHub-tárolónkban](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "További információ a Használati útmutatóban."; /* No comment provided by engineer. */ "Receipts are disabled" = "A kézbesítési jelentések le vannak tiltva"; @@ -4412,12 +4807,36 @@ swipe action */ /* call status */ "rejected call" = "elutasított hívás"; +/* member role */ +"relay" = "átjátszó"; + +/* No comment provided by engineer. */ +"Relay" = "Átjátszó"; + +/* alert title */ +"Relay address" = "Átjátszó címe"; + +/* alert title */ +"Relay connection failed" = "Nem sikerült kapcsolódni az átjátszóhoz"; + +/* No comment provided by engineer. */ +"Relay link" = "Átjátszóhivatkozás"; + +/* alert message */ +"Relay results:" = "Átjátszóeredmények:"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "Az átjátszó csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "Az átjátszó megvédi az IP-címét, de megfigyelheti a hívás időtartamát."; +/* No comment provided by engineer. */ +"Relay test failed!" = "Nem sikerült tesztelni az átjátszót!"; + +/* No comment provided by engineer. */ +"Reliability: many relays per channel." = "Megbízhatóság: több átjátszó is használható csatornánként."; + /* alert action */ "Remove" = "Eltávolítás"; @@ -4442,12 +4861,24 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Eltávolítja a jelmondatot a kulcstartóból?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Feliratkozó eltávolítása"; + +/* alert title */ +"Remove subscriber?" = "Eltávolítja a feliratkozót?"; + /* No comment provided by engineer. */ "removed" = "eltávolítva"; +/* receive error chat item */ +"removed (%d attempts)" = "eltávolítva (%d kísérlet)"; + /* rcv group event chat item */ "removed %@" = "eltávolította őt: %@"; +/* No comment provided by engineer. */ +"removed by operator" = "az üzemeltető eltávolította"; + /* profile update event chat item */ "removed contact address" = "eltávolította a kapcsolattartási címet"; @@ -4616,6 +5047,9 @@ swipe action */ /* No comment provided by engineer. */ "Run chat" = "Csevegési szolgáltatás indítása"; +/* No comment provided by engineer. */ +"Safe web links" = "Biztonságos webhivatkozások"; + /* No comment provided by engineer. */ "Safely receive files" = "Fájlok biztonságos fogadása"; @@ -4632,6 +5066,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Mentés (és a tagok értesítése)"; +/* alert button */ +"Save (and notify subscribers)" = "Mentés (és a feliratkozók értesítése)"; + /* alert title */ "Save admission settings?" = "Menti a befogadási beállításokat?"; @@ -4641,12 +5078,21 @@ chat item action */ /* No comment provided by engineer. */ "Save and notify group members" = "Mentés és a csoporttagok értesítése"; +/* No comment provided by engineer. */ +"Save and notify subscribers" = "Mentés és a feliratkozók értesítése"; + /* No comment provided by engineer. */ "Save and reconnect" = "Mentés és újrakapcsolódás"; /* No comment provided by engineer. */ "Save and update group profile" = "Mentés és a csoportprofil frissítése"; +/* No comment provided by engineer. */ +"Save channel profile" = "Csatornaprofil mentése"; + +/* alert title */ +"Save channel profile?" = "Menti a csatornaprofilt?"; + /* No comment provided by engineer. */ "Save group profile" = "Csoportprofil mentése"; @@ -4741,7 +5187,7 @@ chat item action */ "Search links" = "Hivatkozások keresése"; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Keresés vagy SimpleX-hivatkozás beillesztése"; +"Search or paste SimpleX link" = "Keressen vagy adjon meg egy SimpleX-hivatkozást"; /* No comment provided by engineer. */ "Search videos" = "Videók keresése"; @@ -4776,6 +5222,9 @@ chat item action */ /* chat item text */ "security code changed" = "biztonsági kódja módosult"; +/* No comment provided by engineer. */ +"Security: owners hold channel keys." = "Biztonság: a csatornák kulcsait a tulajdonosok őrzik."; + /* chat item action */ "Select" = "Kiválasztás"; @@ -4854,12 +5303,18 @@ chat item action */ /* No comment provided by engineer. */ "Send request without message" = "Kérés küldése üzenet nélkül"; +/* No comment provided by engineer. */ +"Send the link via any messenger - it's secure. Ask to paste into SimpleX." = "Küldje el a hivatkozást bármilyen üzenetváltó alkalmazáson keresztül – ez egy biztonságos módszer – és kérje meg a partnerét, hogy illessze be a SimpleX alkalmazásba."; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Küldje el őket a galériából vagy az egyéni billentyűzetekről."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Legfeljebb az utolsó 100 üzenet elküldése az új feliratkozók számára."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Küldjön privát visszajelzést a csoportoknak."; @@ -4869,6 +5324,9 @@ chat item action */ /* No comment provided by engineer. */ "Sender may have deleted the connection request." = "A kérés küldője törölhette a kapcsolódási kérést."; +/* alert message */ +"Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later." = "A hivatkozáselőnézet küldése felfedheti az Ön IP-címét a weboldal számára. Ezt később módosíthatja az adatvédelmi beállításokban."; + /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára."; @@ -4947,6 +5405,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "a kiszolgáló várólista információi: %1$@\n\nutoljára fogadott üzenet: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "A kiszolgáló hitelesítést igényel az átjátszóhoz való kapcsolódáshoz, ellenőrizze a jelszavát."; + /* server test error */ "Server requires authorization to create queues, check password." = "A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze a jelszavát."; @@ -5031,6 +5492,12 @@ chat item action */ /* alert message */ "Settings were changed." = "A beállítások módosultak."; +/* No comment provided by engineer. */ +"Setup notifications" = "Értesítések beállítása"; + +/* No comment provided by engineer. */ +"Setup routers" = "Útválasztók beállítása"; + /* No comment provided by engineer. */ "Shape profile images" = "Profilkép alakzata"; @@ -5051,7 +5518,10 @@ chat item action */ "Share address publicly" = "Cím nyilvános megosztása"; /* alert title */ -"Share address with contacts?" = "Megosztja a címet a partnereivel?"; +"Share address with SimpleX contacts?" = "Megosztja a címet a SimpleX partnereivel?"; + +/* No comment provided by engineer. */ +"Share channel" = "Csatorna megosztása"; /* No comment provided by engineer. */ "Share from other apps." = "Megosztás más alkalmazásokból."; @@ -5068,6 +5538,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Profil megosztása"; +/* No comment provided by engineer. */ +"Share relay address" = "Átjátszó címének megosztása"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "SimpleX-cím megosztása a közösségi médiában."; @@ -5078,7 +5551,10 @@ chat item action */ "Share to SimpleX" = "Megosztás a SimpleXben"; /* No comment provided by engineer. */ -"Share with contacts" = "Megosztás a partnerekkel"; +"Share via chat" = "Megosztás egy csevegésen keresztül"; + +/* No comment provided by engineer. */ +"Share with SimpleX contacts" = "Megosztás a SimpleX partnerekkel"; /* No comment provided by engineer. */ "Share your address" = "Saját cím megosztása"; @@ -5129,7 +5605,7 @@ chat item action */ "SimpleX Address" = "SimpleX-cím"; /* No comment provided by engineer. */ -"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül."; +"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó alkalmazáson keresztül."; /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?"; @@ -5182,6 +5658,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "A SimpleX protokollokat a Trail of Bits auditálta."; +/* simplex link type */ +"SimpleX relay address" = "SimpleX-átjátszó címe"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognitómód"; @@ -5234,6 +5713,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "szabványos végpontok közötti titkosítás"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Csillagozás a GitHubon"; + /* No comment provided by engineer. */ "Start chat" = "Csevegés elindítása"; @@ -5300,6 +5782,48 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Feliratkozva"; +/* No comment provided by engineer. */ +"Subscriber" = "Feliratkozó"; + +/* chat feature */ +"Subscriber reports" = "Feliratkozók jelentései"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "A feliratkozó el lesz távolítva a csatornából – ez a művelet nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Feliratkozók"; + +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "A feliratkozók reakciókat adhatnak hozzá az üzenetekhez."; + +/* No comment provided by engineer. */ +"Subscribers can chat with admins." = "A feliratkozók cseveghetnek az adminisztrátorokkal."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "A feliratkozók véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "A feliratkozók jelenthetik az üzeneteket a moderátorok felé."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "A feliratkozók küldhetnek egymásnak közvetlen üzeneteket."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "A feliratkozók küldhetnek eltűnő üzeneteket."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "A feliratkozók küldhetnek fájlokat és médiatartalmakat."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "A feliratkozók küldhetnek SimpleX-hivatkozásokat."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "A feliratkozók küldhetnek hangüzeneteket."; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához."; + /* No comment provided by engineer. */ "Subscription errors" = "Feliratkozási hibák"; @@ -5327,6 +5851,9 @@ report reason */ /* No comment provided by engineer. */ "Take picture" = "Kép készítése"; +/* No comment provided by engineer. */ +"Talk to someone" = "Beszélgessen valakivel"; + /* No comment provided by engineer. */ "Tap button " = "Koppintson a "; @@ -5340,7 +5867,7 @@ report reason */ "Tap Connect to use bot" = "Koppintson a „Kapcsolódás” gombra a bot használatához"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz."; +"Tap Join channel" = "Koppintson a „Csatlakozás a csatornához” gombra"; /* No comment provided by engineer. */ "Tap Join group" = "Koppintson a „Csatlakozás a csoporthoz” gombra"; @@ -5357,6 +5884,9 @@ report reason */ /* No comment provided by engineer. */ "Tap to join incognito" = "Koppintson ide az inkognitóban való kapcsolódáshoz"; +/* No comment provided by engineer. */ +"Tap to open" = "Koppintson ide a megnyitáshoz"; + /* No comment provided by engineer. */ "Tap to paste link" = "Koppintson ide a hivatkozás beillesztéséhez"; @@ -5394,6 +5924,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Értesítések tesztelése"; +/* No comment provided by engineer. */ +"Test relay" = "Átjátszó tesztelése"; + /* No comment provided by engineer. */ "Test server" = "Kiszolgáló tesztelése"; @@ -5421,6 +5954,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "Az alkalmazás %lld sikertelen letöltési kísérlet után eltávolította ezt az üzenetet."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését."; @@ -5430,6 +5966,9 @@ server test failure */ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX-hivatkozás."; +/* conn error description */ +"The connection reached the limit of undelivered messages" = "A kapcsolat elérte a kézbesítetlen üzenetek korlátját"; + /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van."; @@ -5446,7 +5985,7 @@ server test failure */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; /* No comment provided by engineer. */ -"The future of messaging" = "Az üzenetváltás jövője"; +"The first network where you own\nyour contacts and groups." = "Az első hálózat, ahol Ön birtokolja\na saját kapcsolatait és csoportjait."; /* No comment provided by engineer. */ "The hash of the previous message is different." = "Az előző üzenet kivonata különbözik."; @@ -5472,6 +6011,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "A legrégebbi emberi szabadság - beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének - olyan infrastruktúrán alapul, amely nem tudja elárulni."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**."; @@ -5499,6 +6041,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Témák"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot - nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött - telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Ezek a feltételek lesznek elfogadva a következő számára is: **%@**."; @@ -5541,6 +6089,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Ez a csoport már nem létezik."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Ez egy csevegési átjátszó címe, nem használható kapcsolódásra."; + +/* new chat action */ +"This is your link for channel %@!" = "Ez a saját hivatkozása a(z) %@ nevű csatornához!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől."; @@ -5574,6 +6128,9 @@ server test failure */ /* No comment provided by engineer. */ "To make a new connection" = "Új kapcsolat létrehozásához"; +/* No comment provided by engineer. */ +"To make SimpleX Network last." = "A SimpleX hálózat hosszú távú működésének biztosítása érdekében."; + /* No comment provided by engineer. */ "To protect against your link being replaced, you can compare contact security codes." = "A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat a partnerével."; @@ -5622,9 +6179,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Csevegési lista ki/be:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Inkognitó profil használata kapcsolódáskor ki/be."; @@ -5634,6 +6188,9 @@ server test failure */ /* No comment provided by engineer. */ "Toolbar opacity" = "Eszköztár átlátszatlansága"; +/* No comment provided by engineer. */ +"Top bar" = "Felső sáv"; + /* No comment provided by engineer. */ "Total" = "Összes kapcsolat"; @@ -5673,6 +6230,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Feloldja a tag letiltását?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Az összes feliratkozó számára feloldja a feliratkozó letiltását?"; + /* rcv group event chat item */ "unblocked %@" = "feloldotta %@ letiltását"; @@ -5745,12 +6305,15 @@ server test failure */ /* swipe action */ "Unread" = "Olvasatlan"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Nem támogatott kapcsolattartási hivatkozás"; /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Legfeljebb az utolsó 100 üzenet lesz elküldve az új feliratkozók számára."; + /* No comment provided by engineer. */ "Update" = "Frissítés"; @@ -5763,6 +6326,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Frissíti a beállításokat?"; +/* rcv group event chat item */ +"updated channel profile" = "frissített csatornaprofil"; + /* No comment provided by engineer. */ "Updated conditions" = "Frissített feltételek"; @@ -5820,9 +6386,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "%@ használata"; -/* No comment provided by engineer. */ -"Use chat" = "SimpleX Chat használata"; - /* new chat action */ "Use current profile" = "Jelenlegi profil használata"; @@ -5832,6 +6395,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Használat az üzenetekhez"; +/* No comment provided by engineer. */ +"Use for new channels" = "Használat új csatornákhoz"; + /* No comment provided by engineer. */ "Use for new connections" = "Használat új kapcsolatokhoz"; @@ -5856,6 +6422,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Privát útválasztás használata az ismeretlen kiszolgálókhoz."; +/* No comment provided by engineer. */ +"Use relay" = "Átjátszó használata"; + /* No comment provided by engineer. */ "Use server" = "Kiszolgáló használata"; @@ -5880,6 +6449,9 @@ server test failure */ /* No comment provided by engineer. */ "Use the app with one hand." = "Alkalmazás egy kézzel való használata."; +/* No comment provided by engineer. */ +"Use this address in your social media profile, website, or email signature." = "Használja ezt a címet a közösségi oldalakon használt profiljaiban, weboldalakon vagy az e-mail aláírásában."; + /* No comment provided by engineer. */ "Use web port" = "Webport használata"; @@ -5898,6 +6470,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Ellenőrzés"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Kód ellenőrzése a számítógépen"; @@ -5919,6 +6494,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Biztonsági kód ellenőrzése"; +/* relay hostname */ +"via %@" = "a következőn keresztül: %@"; + /* No comment provided by engineer. */ "Via browser" = "Böngészőn keresztül"; @@ -5988,9 +6566,18 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "A hangüzenetek le vannak tiltva!"; +/* alert action */ +"Wait" = "Várakozás"; + +/* relay test step */ +"Wait response" = "Várakozás a válaszra"; + /* No comment provided by engineer. */ "waiting for answer…" = "várakozás a válaszra…"; +/* No comment provided by engineer. */ +"Waiting for channel owner to add relays." = "Várakozás a csatorna tulajdonosára az átjátszók hozzáadásához."; + /* No comment provided by engineer. */ "waiting for confirmation…" = "várakozás a visszaigazolásra…"; @@ -6021,6 +6608,9 @@ server test failure */ /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Figyelmeztetés: néhány adat elveszhet!"; +/* No comment provided by engineer. */ +"We made connecting simpler for new users." = "Az új felhasználók számára egyszerűbbé tettük a kapcsolatok létrehozását."; + /* No comment provided by engineer. */ "WebRTC ICE servers" = "WebRTC ICE-kiszolgálók"; @@ -6057,6 +6647,9 @@ server test failure */ /* 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." = "Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt."; +/* No comment provided by engineer. */ +"Why SimpleX is built." = "Miért jött létre a SimpleX?"; + /* No comment provided by engineer. */ "WiFi" = "Wi-Fi"; @@ -6156,6 +6749,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "Ön megfigyelő"; +/* No comment provided by engineer. */ +"you are subscriber" = "Ön feliratkozó"; + /* snd group event chat item */ "you blocked %@" = "Ön letiltotta őt: %@"; @@ -6198,6 +6794,9 @@ server test failure */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "A lezárási képernyő értesítési előnézetét az „Értesítések” menüben állíthatja be."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the channel." = "Megoszthat egy hivatkozást vagy egy QR-kódot – bárki képes lesz csatlakozni a csatornához."; + /* No comment provided by engineer. */ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoporthivatkozást később törli, akkor nem fogja elveszíteni a csoport meglévő tagjait."; @@ -6238,10 +6837,13 @@ server test failure */ "you changed role of %@ to %@" = "Ön a következőre módosította %1$@ szerepkörét: „%2$@”"; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Nem sikerült ellenőrizni; próbálja meg újra."; +"You commit to:\n- Only legal content in public groups\n- Respect other users - no spam" = "Ön kijelenti, hogy:\n- nyilvános csoportokban kizárólag megengedett tartalmakat oszt meg\n- tiszteletben tartja a többi felhasználót – nem küld senkinek kéretlen tartalmat"; /* No comment provided by engineer. */ -"You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; +"You connected to the channel via this relay link." = "Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához."; + +/* No comment provided by engineer. */ +"You could not be verified; please try again." = "Nem sikerült ellenőrizni; próbálja meg újra."; /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ön már küldött egy kapcsolódási kérést!\nMegismétli a kapcsolódási kérést?"; @@ -6297,6 +6899,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "Ön feloldotta %@ letiltását"; +/* No comment provided by engineer. */ +"You were born without an account" = "Fiók nélkül születtünk."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Csak azután tud üzeneteket küldeni, **miután a kérését elfogadták**."; @@ -6318,6 +6923,9 @@ server test failure */ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Ön nem fog több üzenetet kapni ebből a csatornából. A csevegési előzmények megmaradnak."; + /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak."; @@ -6342,6 +6950,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Hívások"; +/* No comment provided by engineer. */ +"Your channel" = "Saját csatorna"; + /* No comment provided by engineer. */ "Your chat database" = "Csevegési adatbázis"; @@ -6372,6 +6983,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "A partnereivel továbbra is kapcsolatban marad."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "A hitelesítési adatai titkosítatlanul is elküldhetők."; @@ -6387,6 +7001,9 @@ server test failure */ /* No comment provided by engineer. */ "Your ICE servers" = "Saját ICE-kiszolgálók"; +/* No comment provided by engineer. */ +"Your network" = "Saját hálózat"; + /* No comment provided by engineer. */ "Your preferences" = "Beállítások"; @@ -6396,6 +7013,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Saját profil"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "A(z) **%@** nevű profilja meg lesz osztva a csatorna átjátszóival és feliratkozóival.\nAz átjátszók hozzáférhetnek a csatornaüzenetekhez."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja meg lesz osztva."; @@ -6408,11 +7028,20 @@ server test failure */ /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha menti, akkor a profilfrissítés el lesz küldve a partnerei számára."; +/* No comment provided by engineer. */ +"Your public address" = "Saját nyilvános cím"; + /* No comment provided by engineer. */ "Your random profile" = "Véletlenszerű profil"; /* No comment provided by engineer. */ -"Your server address" = "Saját SMP-kiszolgálójának címe"; +"Your relay address" = "Saját átjátszó címe"; + +/* No comment provided by engineer. */ +"Your relay name" = "Saját átjátszó neve"; + +/* No comment provided by engineer. */ +"Your server address" = "Saját SMP-kiszolgáló címe"; /* No comment provided by engineer. */ "Your servers" = "Saját kiszolgálók"; diff --git a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings index 8b56c51595..d1b68ad52c 100644 --- a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings @@ -2,7 +2,7 @@ "CFBundleName" = "SimpleX"; /* Privacy - Camera Usage Description */ -"NSCameraUsageDescription" = "A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz."; +"NSCameraUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a kamerához a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz."; /* Privacy - Face ID Usage Description */ "NSFaceIDUsageDescription" = "A SimpleX Face ID-t használ a helyi hitelesítéshez"; @@ -11,7 +11,7 @@ "NSLocalNetworkUsageDescription" = "A SimpleX helyi hálózati hozzáférést használ, hogy lehetővé tegye a felhasználói csevegési profil használatát számítógépen keresztül ugyanazon a hálózaton."; /* Privacy - Microphone Usage Description */ -"NSMicrophoneUsageDescription" = "A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez."; +"NSMicrophoneUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a mikrofonhoz a hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez."; /* Privacy - Photo Library Additions Usage Description */ "NSPhotoLibraryAddUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a galériához a rögzített és fogadott média mentéséhez"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 69045c9c23..96b117eeca 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -10,6 +10,9 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- recapito dei messaggi più stabile.\n- gruppi un po' migliorati.\n- e altro ancora!"; +/* No comment provided by engineer. */ +"- opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking." = "- scegli se inviare anteprime dei link.\n- previeni il phishing dei collegamenti ipertestuali.\n- rimuovi il tracciamento dei link."; + /* No comment provided by engineer. */ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- avvisa facoltativamente i contatti eliminati.\n- nomi del profilo con spazi.\n- e molto altro!"; @@ -19,21 +22,21 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 colorato!"; +/* chat link info line */ +"(from owner)" = "(dal proprietario)"; + /* No comment provided by engineer. */ "(new)" = "(nuovo)"; +/* chat link info line */ +"(signed)" = "(firmato)"; + /* No comment provided by engineer. */ "(this device v%@)" = "(questo dispositivo v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuisci](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Inviaci un'email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Aggiungi contatto**: per creare un nuovo link di invito."; @@ -64,6 +67,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Scansiona / Incolla link**: per connetterti tramite un link che hai ricevuto."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Prova il relay** per recuperare il suo nome."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi."; @@ -175,6 +181,18 @@ /* time interval */ "%d months" = "%d mesi"; +/* channel relay bar +channel subscriber relay bar */ +"%d relays failed" = "%d relay falliti"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays not active" = "%d relay non attivi"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays removed" = "%d relay rimossi"; + /* time interval */ "%d sec" = "%d sec"; @@ -184,15 +202,50 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d messaggio/i saltato/i"; +/* channel subscriber count */ +"%d subscriber" = "%d iscritto"; + +/* channel subscriber count */ +"%d subscribers" = "%d iscritti"; + /* time interval */ "%d weeks" = "%d settimane"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d relay attivo/i"; + +/* channel relay bar */ +"%d/%d relays active, %d errors" = "%1$d/%2$d relay attivi, %3$d errori"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d relay attivo/i, %3$d fallito/i"; + +/* channel relay bar */ +"%d/%d relays active, %d removed" = "%1$d/%2$d relay attivi, %3$d rimossi"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d relay connesso/i"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d relay connesso/i, %3$d errori"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d failed" = "%1$d/%2$d relay connessi, %3$d falliti"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d removed" = "%1$d/%2$d relay connessi, %3$d rimossi"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld eventi del canale"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld contatto/i selezionato/i"; @@ -262,6 +315,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~barrato~"; +/* owner verification */ +"⚠️ Signature verification failed: %@." = "⚠️ Verifica della firma fallita: %@."; + /* time to disappear */ "0 sec" = "0 sec"; @@ -307,6 +363,9 @@ time interval */ /* No comment provided by engineer. */ "A few more things" = "Qualche altra cosa"; +/* No comment provided by engineer. */ +"A link for one person to connect" = "Un link per una persona da connettere"; + /* notification title */ "A new contact" = "Un contatto nuovo"; @@ -371,6 +430,9 @@ swipe action */ /* alert title */ "Accept member" = "Accetta membro"; +/* No comment provided by engineer. */ +"accepted" = "accettato"; + /* rcv group event chat item */ "accepted %@" = "%@ accettato"; @@ -392,6 +454,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Errori di riconoscimento"; +/* No comment provided by engineer. */ +"active" = "attivo"; + /* token status text */ "Active" = "Attivo"; @@ -399,7 +464,7 @@ swipe action */ "Active connections" = "Connessioni attive"; /* 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." = "Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; +"Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts." = "Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti di SimpleX possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX."; /* No comment provided by engineer. */ "Add friends" = "Aggiungi amici"; @@ -440,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "Added message servers" = "Server dei messaggi aggiunti"; +/* No comment provided by engineer. */ +"Adding relays will be supported later." = "L'aggiunta di relay verrà supportata prossimamente."; + /* No comment provided by engineer. */ "Additional accent" = "Principale aggiuntivo"; @@ -530,6 +598,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Tutti gli profili"; +/* No comment provided by engineer. */ +"All relays failed" = "Tutti i relay falliti"; + +/* No comment provided by engineer. */ +"All relays removed" = "Tutti i relay rimossi"; + /* No comment provided by engineer. */ "All reports will be archived for you." = "Tutte le segnalazioni verranno archiviate per te."; @@ -566,6 +640,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore)"; +/* No comment provided by engineer. */ +"Allow members to chat with admins." = "Consenti ai membri di chattare con gli amministratori."; + /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Consenti reazioni ai messaggi solo se il tuo contatto le consente."; @@ -575,12 +652,18 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Permetti l'invio di messaggi diretti ai membri."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Permetti l'invio di messaggi diretti agli iscritti."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Permetti l'invio di messaggi a tempo."; /* No comment provided by engineer. */ "Allow sharing" = "Consenti la condivisione"; +/* No comment provided by engineer. */ +"Allow subscribers to chat with admins." = "Consenti agli iscritti di chattare con gli amministratori."; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore)"; @@ -650,9 +733,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Rispondi alla chiamata"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Chiunque può installare i server."; - /* No comment provided by engineer. */ "App build: %@" = "Build dell'app: %@"; @@ -771,7 +851,7 @@ swipe action */ "Auto-accept contact requests" = "Auto-accetta le richieste di contatto"; /* No comment provided by engineer. */ -"Auto-accept images" = "Auto-accetta le immagini"; +"Auto-accept images" = "Accetta automaticamente le immagini"; /* No comment provided by engineer. */ "Back" = "Indietro"; @@ -794,6 +874,15 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "ID del messaggio errato"; +/* No comment provided by engineer. */ +"Be free\nin your network" = "Vivi libero\nnella tua rete"; + +/* No comment provided by engineer. */ +"Be free in your network." = "Vivi libero nella tua rete."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Perché abbiamo distrutto il potere di sapere chi sei. In modo che il tuo potere non possa mai esserti sottratto."; + /* No comment provided by engineer. */ "Better calls" = "Chiamate migliorate"; @@ -851,6 +940,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Bloccare il membro?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Bloccare l'iscritto per tutti?"; + /* marked deleted chat item preview text */ "blocked" = "bloccato"; @@ -895,9 +987,15 @@ marked deleted chat item preview text */ "Both you and your contact can send voice messages." = "Sia tu che il tuo contatto potete inviare messaggi vocali."; /* No comment provided by engineer. */ -"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +"Bottom bar" = "Barra inferiore"; + +/* compose placeholder for channel owner */ +"Broadcast" = "Trasmetti"; /* No comment provided by engineer. */ +"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* chat link info line */ "Business address" = "Indirizzo di lavoro"; /* No comment provided by engineer. */ @@ -912,9 +1010,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Usando SimpleX Chat accetti di:\n- inviare solo contenuto legale nei gruppi pubblici.\n- rispettare gli altri utenti - niente spam."; - /* No comment provided by engineer. */ "call" = "chiama"; @@ -939,6 +1034,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Camera not available" = "Fotocamera non disponibile"; +/* No comment provided by engineer. */ +"can't broadcast" = "impossibile trasmettere"; + /* No comment provided by engineer. */ "Can't call contact" = "Impossibile chiamare il contatto"; @@ -1038,6 +1136,58 @@ set passcode view */ /* chat item text */ "changing address…" = "cambio indirizzo…"; +/* shown as sender role for channel messages */ +"channel" = "canale"; + +/* No comment provided by engineer. */ +"Channel" = "Canale"; + +/* No comment provided by engineer. */ +"Channel display name" = "Nome da mostrare del canale"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Nome completo del canale (facoltativo)"; + +/* alert message +alert subtitle */ +"Channel has no active relays. Please try to join later." = "Il canale non ha relay attivi. Prova a iscriverti più tardi."; + +/* No comment provided by engineer. */ +"Channel image" = "Immagine del canale"; + +/* chat link info line */ +"Channel link" = "Link del canale"; + +/* No comment provided by engineer. */ +"Channel preferences" = "Preferenze del canale"; + +/* No comment provided by engineer. */ +"Channel profile" = "Profilo del canale"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "Il profilo del canale è memorizzato sui dispositivi degli iscritti e sui relay di chat."; + +/* snd group event chat item */ +"channel profile updated" = "profilo del canale aggiornato"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "Il profilo del canale è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato agli iscritti di canale."; + +/* alert title */ +"Channel temporarily unavailable" = "Canale non disponibile temporaneamente"; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "Il canale verrà eliminato per tutti gli iscritti, non è reversibile!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "Il canale verrà eliminato per te, non è reversibile!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "Il canale sarà operativo con %1$d di %2$d relay. Procedere?"; + +/* No comment provided by engineer. */ +"Channels" = "Canali"; + /* No comment provided by engineer. */ "Chat" = "Chat"; @@ -1089,6 +1239,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Profilo utente"; +/* No comment provided by engineer. */ +"Chat relay" = "Relay di chat"; + +/* No comment provided by engineer. */ +"Chat relays" = "Relay di chat"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "I relay di chat inoltrano i messaggi nei canali che crei."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "I relay di chat inoltrano i messaggi agli iscritti del canale."; + /* No comment provided by engineer. */ "Chat theme" = "Tema della chat"; @@ -1098,7 +1260,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chat con amministratori"; /* No comment provided by engineer. */ @@ -1110,15 +1273,30 @@ set passcode view */ /* No comment provided by engineer. */ "Chats" = "Chat"; +/* No comment provided by engineer. */ +"Chats with admins are prohibited." = "Le chat con gli amministratori sono vietate."; + +/* alert message */ +"Chats with admins in public channels have no E2E encryption - use only with trusted chat relays." = "Le chat con amministratori in canali pubblici non hanno crittografia E2E: usale solo con relay di chat fidati."; + /* No comment provided by engineer. */ "Chats with members" = "Chat con membri"; +/* No comment provided by engineer. */ +"Chats with members are disabled" = "Le chat con i membri sono disattivate"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Controlla i messaggi ogni 20 min."; /* No comment provided by engineer. */ "Check messages when allowed." = "Controlla i messaggi quando consentito."; +/* alert message */ +"Check relay address and try again." = "Controlla l'indirizzo del relay e riprova."; + +/* alert message */ +"Check relay name and try again." = "Controlla il nome del relay e riprova."; + /* alert title */ "Check server address and try again." = "Controlla l'indirizzo del server e riprova."; @@ -1213,7 +1391,7 @@ set passcode view */ "Configure ICE servers" = "Configura server ICE"; /* No comment provided by engineer. */ -"Configure server operators" = "Configura gli operatori dei server"; +"Configure relays" = "Configura i relay"; /* No comment provided by engineer. */ "Confirm" = "Conferma"; @@ -1279,6 +1457,9 @@ server test step */ /* new chat sheet title */ "Connect via link" = "Connetti via link"; +/* No comment provided by engineer. */ +"Connect via link or QR code" = "Connetti via link o codice QR"; + /* new chat sheet title */ "Connect via one-time link" = "Connetti via link una tantum"; @@ -1348,7 +1529,7 @@ server test step */ /* alert title */ "Connection error" = "Errore di connessione"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Errore di connessione (AUTH)"; /* chat list item title (it should not be shown */ @@ -1393,6 +1574,9 @@ server test step */ /* profile update event chat item */ "contact %@ changed to %@" = "contatto %1$@ cambiato in %2$@"; +/* chat link info line */ +"Contact address" = "Indirizzo di contatto"; + /* No comment provided by engineer. */ "Contact allows" = "Il contatto lo consente"; @@ -1453,6 +1637,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Continua"; +/* No comment provided by engineer. */ +"Contribute" = "Contribuisci"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Conversazione eliminata!"; @@ -1471,9 +1658,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Correggere il nome a %@?"; -/* No comment provided by engineer. */ -"Create" = "Crea"; - /* No comment provided by engineer. */ "Create 1-time link" = "Crea link una tantum"; @@ -1501,6 +1685,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Crea profilo"; +/* No comment provided by engineer. */ +"Create public channel" = "Crea canale pubblico"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Crea canale pubblico (BETA)"; + /* server test step */ "Create queue" = "Crea coda"; @@ -1510,9 +1700,15 @@ server test step */ /* No comment provided by engineer. */ "Create your address" = "Crea il tuo indirizzo"; +/* No comment provided by engineer. */ +"Create your link" = "Connettiti con qualcuno"; + /* No comment provided by engineer. */ "Create your profile" = "Crea il tuo profilo"; +/* No comment provided by engineer. */ +"Create your public address" = "Crea il tuo indirizzo pubblico"; + /* No comment provided by engineer. */ "Created" = "Creato"; @@ -1525,6 +1721,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Creazione link dell'archivio"; +/* No comment provided by engineer. */ +"Creating channel" = "Creazione canale"; + /* No comment provided by engineer. */ "Creating link…" = "Creazione link…"; @@ -1627,8 +1826,8 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Debug della consegna"; -/* No comment provided by engineer. */ -"Decentralized" = "Decentralizzato"; +/* relay test step */ +"Decode link" = "Decodifica il link"; /* message decrypt error item */ "Decryption error" = "Errore di decifrazione"; @@ -1671,6 +1870,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Elimina e avvisa il contatto"; +/* No comment provided by engineer. */ +"Delete channel" = "Elimina canale"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Eliminare il canale?"; + /* No comment provided by engineer. */ "Delete chat" = "Elimina chat"; @@ -1774,6 +1979,9 @@ alert button */ /* server test step */ "Delete queue" = "Elimina coda"; +/* No comment provided by engineer. */ +"Delete relay" = "Elimina relay"; + /* No comment provided by engineer. */ "Delete report" = "Elimina la segnalazione"; @@ -1798,6 +2006,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Eliminato il: %@"; +/* rcv group event chat item */ +"deleted channel" = "canale eliminato"; + /* rcv direct event chat item */ "deleted contact" = "contatto eliminato"; @@ -1888,6 +2099,12 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "I messaggi diretti tra i membri sono vietati in questo gruppo."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "I messaggi diretti tra gli iscritti sono vietati."; + +/* alert button */ +"Disable" = "Disattiva"; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Disattiva (mantieni sostituzioni)"; @@ -1945,6 +2162,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Non inviare la cronologia ai nuovi membri."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Non inviare la cronologia ai nuovi iscritti."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NON inviare messaggi direttamente, anche se il tuo server o quello di destinazione non supporta l'instradamento privato."; @@ -2024,27 +2244,39 @@ chat item action */ /* No comment provided by engineer. */ "E2E encrypted notifications." = "Notifiche crittografate E2E."; +/* No comment provided by engineer. */ +"Easier to invite your friends 👋" = "È più facile invitare i tuoi amici 👋"; + /* chat item action */ "Edit" = "Modifica"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Modifica profilo canale"; + /* No comment provided by engineer. */ "Edit group profile" = "Modifica il profilo del gruppo"; /* No comment provided by engineer. */ "Empty message!" = "Messaggio vuoto!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Attiva"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Attiva (mantieni sostituzioni)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Attiva almeno un relay di chat in \"Rete e server\"."; + /* alert title */ "Enable automatic message deletion?" = "Attivare l'eliminazione automatica dei messaggi?"; /* No comment provided by engineer. */ "Enable camera access" = "Attiva l'accesso alla fotocamera"; +/* alert title */ +"Enable chats with admins?" = "Attivare le chat con gli amministratori?"; + /* No comment provided by engineer. */ "Enable disappearing messages by default." = "Attiva i messaggi a tempo in modo predefinito."; @@ -2060,11 +2292,11 @@ chat item action */ /* No comment provided by engineer. */ "Enable instant notifications?" = "Attivare le notifiche istantanee?"; -/* No comment provided by engineer. */ -"Enable lock" = "Attiva blocco"; +/* alert title */ +"Enable link previews?" = "Attivare le anteprime dei link?"; /* No comment provided by engineer. */ -"Enable notifications" = "Attiva le notifiche"; +"Enable lock" = "Attiva blocco"; /* No comment provided by engineer. */ "Enable periodic notifications?" = "Attivare le notifiche periodiche?"; @@ -2171,6 +2403,9 @@ chat item action */ /* call status */ "ended call %@" = "chiamata terminata %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Inserisci il nome del canale…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Inserisci la password giusta."; @@ -2189,6 +2424,12 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Inserisci la password sopra per mostrare!"; +/* No comment provided by engineer. */ +"Enter profile name..." = "Inserisci nome profilo..."; + +/* No comment provided by engineer. */ +"Enter relay name…" = "Inserisci il nome del relay…"; + /* No comment provided by engineer. */ "Enter server manually" = "Inserisci il server a mano"; @@ -2207,7 +2448,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "errore"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Errore"; /* No comment provided by engineer. */ @@ -2225,6 +2466,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; +/* alert title */ +"Error adding relay" = "Errore di aggiunta del relay"; + /* alert title */ "Error adding server" = "Errore di aggiunta del server"; @@ -2261,6 +2505,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Errore nella creazione dell'indirizzo"; +/* alert title */ +"Error creating channel" = "Errore di creazione del canale"; + /* No comment provided by engineer. */ "Error creating group" = "Errore nella creazione del gruppo"; @@ -2366,6 +2613,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Errore di azzeramento statistiche"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Errore di salvataggio del profilo del canale"; + /* alert title */ "Error saving chat list" = "Errore nel salvataggio dell'elenco di chat"; @@ -2408,6 +2658,9 @@ chat item action */ /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Errore nell'impostazione delle ricevute di consegna!"; +/* alert title */ +"Error sharing channel" = "Errore nella condivisione del canale"; + /* No comment provided by engineer. */ "Error starting chat" = "Errore di avvio della chat"; @@ -2450,6 +2703,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Errore: "; +/* receive error chat item */ +"error: %@" = "errore: %@"; + /* alert message file error text snd error text */ @@ -2634,6 +2890,9 @@ server test error */ /* No comment provided by engineer. */ "For all moderators" = "Per tutti i moderatori"; +/* No comment provided by engineer. */ +"For anyone to reach you" = "Per chiunque debba raggiungerti"; + /* servers error servers warning */ "For chat profile %@:" = "Per il profilo di chat %@:"; @@ -2719,9 +2978,15 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Ulteriore riduzione del consumo della batteria"; +/* relay test step */ +"Get link" = "Ottieni link"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Ricevi una notifica quando menzionato."; +/* No comment provided by engineer. */ +"Get started" = "Cominciamo"; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIF e adesivi"; @@ -2767,7 +3032,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "il gruppo è eliminato"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Link del gruppo"; /* No comment provided by engineer. */ @@ -2839,6 +3104,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "La cronologia non viene inviata ai nuovi membri."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "La cronologia non viene inviata ai nuovi iscritti."; + /* time unit */ "hours" = "ore"; @@ -2899,9 +3167,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Immediatamente"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immune a spam e abusi"; - /* No comment provided by engineer. */ "Import" = "Importa"; @@ -3002,7 +3267,7 @@ servers warning */ "Initial role" = "Ruolo iniziale"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installa Simplex Chat per terminale"; /* No comment provided by engineer. */ "Instant" = "Istantaneamente"; @@ -3037,7 +3302,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "dati chat non validi"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Link di connessione non valido"; /* invalid chat item */ @@ -3058,6 +3323,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Codice QR non valido"; +/* alert title */ +"Invalid relay address!" = "Indirizzo del relay non valido!"; + +/* alert title */ +"Invalid relay name!" = "Nome del relay non valido!"; + /* No comment provided by engineer. */ "Invalid response" = "Risposta non valida"; @@ -3085,6 +3356,9 @@ servers warning */ /* No comment provided by engineer. */ "Invite members" = "Invita membri"; +/* No comment provided by engineer. */ +"Invite someone privately" = "Invita qualcuno in modo privato"; + /* No comment provided by engineer. */ "Invite to chat" = "Invita in chat"; @@ -3151,6 +3425,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "entra come %@"; +/* No comment provided by engineer. */ +"Join channel" = "Iscriviti al canale"; + /* new chat sheet title */ "Join group" = "Entra nel gruppo"; @@ -3199,6 +3476,12 @@ servers warning */ /* swipe action */ "Leave" = "Esci"; +/* No comment provided by engineer. */ +"Leave channel" = "Esci dal canale"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Uscire dal canale?"; + /* No comment provided by engineer. */ "Leave chat" = "Esci dalla chat"; @@ -3217,6 +3500,9 @@ servers warning */ /* No comment provided by engineer. */ "Less traffic on mobile networks." = "Meno traffico sulle reti mobili."; +/* No comment provided by engineer. */ +"Let someone connect to you" = "Lascia che qualcuno si connetta a te"; + /* email subject */ "Let's talk in SimpleX Chat" = "Parliamo in SimpleX Chat"; @@ -3226,9 +3512,15 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Limitazioni"; +/* No comment provided by engineer. */ +"link" = "link"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Collega le app mobile e desktop! 🔗"; +/* owner verification */ +"Link signature verified." = "Firma del link verificata."; + /* No comment provided by engineer. */ "Linked desktop options" = "Opzioni del desktop collegato"; @@ -3358,6 +3650,9 @@ servers warning */ /* No comment provided by engineer. */ "Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; +/* No comment provided by engineer. */ +"Members can chat with admins." = "I membri possono chattare con gli amministratori."; + /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; @@ -3400,6 +3695,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Bozza del messaggio"; +/* No comment provided by engineer. */ +"Message error" = "Errore del messaggio"; + /* item status text */ "Message forwarded" = "Messaggio inoltrato"; @@ -3460,6 +3758,12 @@ servers warning */ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "I messaggi da %@ verranno mostrati!"; +/* No comment provided by engineer. */ +"Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages." = "I messaggi in questo canale **non sono crittografati end-to-end**. I relay di chat possono vedere questi messaggi."; + +/* E2EE info chat item */ +"Messages in this channel are not end-to-end encrypted. Chat relays can see these messages." = "I messaggi in questo canale non sono crittografati end-to-end. I relay di chat possono vedere questi messaggi."; + /* alert message */ "Messages in this chat will never be deleted." = "I messaggi in questa chat non verranno mai eliminati."; @@ -3479,10 +3783,10 @@ servers warning */ "Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione."; /* No comment provided by engineer. */ -"Migrate device" = "Migra dispositivo"; +"Migrate" = "Migra"; /* No comment provided by engineer. */ -"Migrate from another device" = "Migra da un altro dispositivo"; +"Migrate device" = "Migra dispositivo"; /* No comment provided by engineer. */ "Migrate here" = "Migra qui"; @@ -3574,12 +3878,18 @@ servers warning */ /* No comment provided by engineer. */ "Network & servers" = "Rete e server"; +/* No comment provided by engineer. */ +"Network commitments" = "Impegni sulla rete"; + /* No comment provided by engineer. */ "Network connection" = "Connessione di rete"; /* No comment provided by engineer. */ "Network decentralization" = "Decentralizzazione della rete"; +/* conn error description */ +"Network error" = "Errore di rete"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo."; @@ -3589,6 +3899,9 @@ servers warning */ /* No comment provided by engineer. */ "Network operator" = "Operatore di rete"; +/* No comment provided by engineer. */ +"Network routers cannot know\nwho talks to whom" = "Gli instradatori di rete non possono\nsapere chi parla con chi"; + /* No comment provided by engineer. */ "Network settings" = "Impostazioni di rete"; @@ -3598,15 +3911,24 @@ servers warning */ /* delete after time */ "never" = "mai"; +/* No comment provided by engineer. */ +"new" = "nuovo"; + /* token status text */ "New" = "Nuovo"; +/* No comment provided by engineer. */ +"New 1-time link" = "Nuovo link una tantum"; + /* No comment provided by engineer. */ "New chat" = "Nuova chat"; /* No comment provided by engineer. */ "New chat experience 🎉" = "Una nuova esperienza di chat 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Nuovo relay di chat"; + /* notification */ "New contact request" = "Nuova richiesta di contatto"; @@ -3664,9 +3986,21 @@ servers warning */ /* No comment provided by engineer. */ "No" = "No"; +/* No comment provided by engineer. */ +"No account. No phone. No email. No ID.\nThe most secure encryption." = "Nessun account. Nessun telefono. Nessuna email. Nessun identificatore.\nLa crittografia più sicura."; + +/* No comment provided by engineer. */ +"No active relays" = "Nessun relay attivo"; + /* Authentication unavailable */ "No app password" = "Nessuna password dell'app"; +/* No comment provided by engineer. */ +"No chat relays" = "Nessun relay di chat"; + +/* servers warning */ +"No chat relays enabled." = "Nessun relay di chat attivato."; + /* No comment provided by engineer. */ "No chats" = "Nessuna chat"; @@ -3764,7 +4098,16 @@ servers warning */ "No unread chats" = "Nessuna chat non letta"; /* No comment provided by engineer. */ -"No user identifiers." = "Nessun identificatore utente."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nessuno monitorava le tue conversazioni. Nessuno disegnava una mappa delle tue posizioni. La privacy non era mai stata una caratteristica, era uno stile di vita."; + +/* No comment provided by engineer. */ +"Non-profit governance" = "Organizzazione non a scopo di lucro"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Non una serratura migliore sulla porta di qualcun altro. Non un padrone di casa più gentile che rispetta la tua privacy, ma che continua a tenere traccia di tutti i visitatori. Non sei un ospite. Sei a casa tua. Nessun re può entrarvi: sei tu il sovrano."; + +/* alert title */ +"Not all relays connected" = "Non tutti i relay sono connessi"; /* No comment provided by engineer. */ "Not compatible!" = "Non compatibile!"; @@ -3822,7 +4165,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3831,9 +4174,15 @@ new chat action */ /* group pref value */ "on" = "on"; +/* No comment provided by engineer. */ +"On your phone, not on servers." = "Sul tuo telefono, non sui server."; + /* No comment provided by engineer. */ "One-time invitation link" = "Link di invito una tantum"; +/* chat link info line */ +"One-time link" = "Link una tantum"; + /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Gli host Onion saranno **necessari** per la connessione.\nRichiede l'attivazione della VPN."; @@ -3843,6 +4192,9 @@ new chat action */ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Gli host Onion non verranno usati."; +/* No comment provided by engineer. */ +"Only channel owners can change channel preferences." = "Solo i proprietari del canale possono modificarne le preferenze."; + /* No comment provided by engineer. */ "Only chat owners can change preferences." = "Solo i proprietari della chat possono modificarne le preferenze."; @@ -3903,12 +4255,16 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Solo il tuo contatto può inviare messaggi vocali."; -/* alert action */ +/* alert action +alert button */ "Open" = "Apri"; /* No comment provided by engineer. */ "Open changes" = "Apri le modifiche"; +/* new chat action */ +"Open channel" = "Apri canale"; + /* new chat action */ "Open chat" = "Apri chat"; @@ -3921,6 +4277,9 @@ new chat action */ /* No comment provided by engineer. */ "Open conditions" = "Apri le condizioni"; +/* alert title */ +"Open external link?" = "Aprire il link esterno?"; + /* alert action */ "Open full link" = "Apri link completo"; @@ -3933,6 +4292,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Apri migrazione ad un altro dispositivo"; +/* new chat action */ +"Open new channel" = "Apri un canale nuovo"; + /* new chat action */ "Open new chat" = "Apri una chat nuova"; @@ -3964,10 +4326,13 @@ new chat action */ "Operator server" = "Server dell'operatore"; /* No comment provided by engineer. */ -"Or import archive file" = "O importa file archivio"; +"Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code" = "Gli operatori si impegnano a:\n- Essere indipendenti\n- Minimizzare l'uso di metadati\n- Eseguire codice open source verificato"; /* No comment provided by engineer. */ -"Or paste archive link" = "O incolla il link dell'archivio"; +"Or import archive file" = "O importa un file dell'archivio"; + +/* No comment provided by engineer. */ +"Or paste archive link" = "O incolla un link dell'archivio"; /* No comment provided by engineer. */ "Or scan QR code" = "O scansiona il codice QR"; @@ -3975,12 +4340,18 @@ new chat action */ /* No comment provided by engineer. */ "Or securely share this file link" = "O condividi in modo sicuro questo link del file"; +/* No comment provided by engineer. */ +"Or show QR in person or via video call." = "O mostra il QR di persona o via videochiamata."; + /* No comment provided by engineer. */ "Or show this code" = "O mostra questo codice"; /* No comment provided by engineer. */ "Or to share privately" = "O per condividere in modo privato"; +/* No comment provided by engineer. */ +"Or use this QR - print or show online." = "O usa questo QR: stampalo o mostralo online."; + /* No comment provided by engineer. */ "Organize chats into lists" = "Organizza le chat in elenchi"; @@ -3999,9 +4370,18 @@ new chat action */ /* member role */ "owner" = "proprietario"; +/* No comment provided by engineer. */ +"Owner" = "Proprietario"; + /* feature role */ "owners" = "proprietari"; +/* No comment provided by engineer. */ +"Owners" = "Proprietari"; + +/* No comment provided by engineer. */ +"Ownership: you can run your own relays." = "Proprietà: puoi gestire i tuoi relay personali."; + /* No comment provided by engineer. */ "Passcode" = "Codice di accesso"; @@ -4029,6 +4409,9 @@ new chat action */ /* No comment provided by engineer. */ "Paste image" = "Incolla immagine"; +/* No comment provided by engineer. */ +"Paste link / Scan" = "Incolla link / Scansiona"; + /* No comment provided by engineer. */ "Paste link to connect!" = "Incolla un link per connettere!"; @@ -4137,6 +4520,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva la bozza dell'ultimo messaggio, con gli allegati."; +/* No comment provided by engineer. */ +"Preset relay address" = "Indirizzo relay preimpostato"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Nome relay preimpostato"; + /* No comment provided by engineer. */ "Preset server address" = "Indirizzo server preimpostato"; @@ -4159,10 +4548,10 @@ new chat action */ "Privacy policy and conditions of use." = "Informativa sulla privacy e condizioni d'uso."; /* No comment provided by engineer. */ -"Privacy redefined" = "Privacy ridefinita"; +"Privacy: for owners and subscribers." = "Privacy: per i proprietari e gli iscritti."; /* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server."; +"Private and secure messaging." = "Messaggistica privata e sicura."; /* No comment provided by engineer. */ "Private filenames" = "Nomi di file privati"; @@ -4188,6 +4577,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Scadenza dell'instradamento privato"; +/* alert action */ +"Proceed" = "Procedi"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profilo e connessioni al server"; @@ -4204,11 +4596,14 @@ new chat action */ "Profile theme" = "Tema del profilo"; /* alert message */ -"Profile update will be sent to your contacts." = "L'aggiornamento del profilo verrà inviato ai tuoi contatti."; +"Profile update will be sent to your SimpleX contacts." = "L'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX."; /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Proibisci le chiamate audio/video."; +/* No comment provided by engineer. */ +"Prohibit chats with admins." = "Vieta le chat con gli amministratori."; + /* No comment provided by engineer. */ "Prohibit irreversible message deletion." = "Proibisci l'eliminazione irreversibile dei messaggi."; @@ -4224,6 +4619,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Proibisci l'invio di messaggi diretti ai membri."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "Proibisci l'invio di messaggi diretti agli iscritti."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Proibisci l'invio di messaggi a tempo."; @@ -4266,6 +4664,9 @@ new chat action */ /* No comment provided by engineer. */ "Proxy requires password" = "Il proxy richiede una password"; +/* No comment provided by engineer. */ +"Public channels - speak freely 🚀" = "Canali pubblici - parla liberamente 🚀"; + /* No comment provided by engineer. */ "Push notifications" = "Notifiche push"; @@ -4294,16 +4695,10 @@ new chat action */ "Read more" = "Leggi tutto"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Maggiori informazioni nel nostro repository GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Maggiori informazioni nella [Guida per l'utente](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)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Maggiori informazioni nel nostro [repository GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Leggi di più nella Guida utente."; /* No comment provided by engineer. */ "Receipts are disabled" = "Le ricevute sono disattivate"; @@ -4412,12 +4807,36 @@ swipe action */ /* call status */ "rejected call" = "chiamata rifiutata"; +/* member role */ +"relay" = "relay"; + +/* No comment provided by engineer. */ +"Relay" = "Relay"; + +/* alert title */ +"Relay address" = "Indirizzo del relay"; + +/* alert title */ +"Relay connection failed" = "Connessione del relay fallita"; + +/* No comment provided by engineer. */ +"Relay link" = "Link del relay"; + +/* alert message */ +"Relay results:" = "Risultati relay:"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "Il server relay protegge il tuo indirizzo IP, ma può osservare la durata della chiamata."; +/* No comment provided by engineer. */ +"Relay test failed!" = "Prova del relay fallita!"; + +/* No comment provided by engineer. */ +"Reliability: many relays per channel." = "Affidabilità: relay multipli per canale."; + /* alert action */ "Remove" = "Rimuovi"; @@ -4442,12 +4861,24 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Rimuovere la password dal portachiavi?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Rimuovi iscritto"; + +/* alert title */ +"Remove subscriber?" = "Rimuovere l'iscritto?"; + /* No comment provided by engineer. */ "removed" = "rimosso"; +/* receive error chat item */ +"removed (%d attempts)" = "rimosso (%d tentativi)"; + /* rcv group event chat item */ "removed %@" = "ha rimosso %@"; +/* No comment provided by engineer. */ +"removed by operator" = "rimosso da un operatore"; + /* profile update event chat item */ "removed contact address" = "indirizzo di contatto rimosso"; @@ -4616,6 +5047,9 @@ swipe action */ /* No comment provided by engineer. */ "Run chat" = "Avvia chat"; +/* No comment provided by engineer. */ +"Safe web links" = "Link web sicuri"; + /* No comment provided by engineer. */ "Safely receive files" = "Ricevi i file in sicurezza"; @@ -4630,7 +5064,10 @@ chat item action */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; /* alert button */ -"Save (and notify members)" = "Salva (e informa i membri)"; +"Save (and notify members)" = "Salva (e avvisa i membri)"; + +/* alert button */ +"Save (and notify subscribers)" = "Salva (e avvisa gli iscritti)"; /* alert title */ "Save admission settings?" = "Salvare le impostazioni di ammissione?"; @@ -4641,12 +5078,21 @@ chat item action */ /* No comment provided by engineer. */ "Save and notify group members" = "Salva e avvisa i membri del gruppo"; +/* No comment provided by engineer. */ +"Save and notify subscribers" = "Salva e avvisa gli iscritti"; + /* No comment provided by engineer. */ "Save and reconnect" = "Salva e riconnetti"; /* No comment provided by engineer. */ "Save and update group profile" = "Salva e aggiorna il profilo del gruppo"; +/* No comment provided by engineer. */ +"Save channel profile" = "Salva il profilo del canale"; + +/* alert title */ +"Save channel profile?" = "Salva il profilo del canale?"; + /* No comment provided by engineer. */ "Save group profile" = "Salva il profilo del gruppo"; @@ -4711,7 +5157,7 @@ chat item action */ "Scan code" = "Scansiona codice"; /* No comment provided by engineer. */ -"Scan QR code" = "Scansiona codice QR"; +"Scan QR code" = "Scansiona un codice QR"; /* No comment provided by engineer. */ "Scan QR code from desktop" = "Scansiona codice QR dal desktop"; @@ -4776,6 +5222,9 @@ chat item action */ /* chat item text */ "security code changed" = "codice di sicurezza modificato"; +/* No comment provided by engineer. */ +"Security: owners hold channel keys." = "Sicurezza: solo i proprietari hanno le chiavi del canale."; + /* chat item action */ "Select" = "Seleziona"; @@ -4854,12 +5303,18 @@ chat item action */ /* No comment provided by engineer. */ "Send request without message" = "Invia richiesta senza messaggio"; +/* No comment provided by engineer. */ +"Send the link via any messenger - it's secure. Ask to paste into SimpleX." = "Invia il link tramite qualsiasi messenger, è sicuro. Chiedi di incollarlo in SimpleX."; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Inviali dalla galleria o dalle tastiere personalizzate."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Invia fino a 100 ultimi messaggi ai nuovi membri."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Invia fino a 100 ultimi messaggi ai nuovi iscritti."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Invia i tuoi commenti privati ai gruppi."; @@ -4869,6 +5324,9 @@ chat item action */ /* No comment provided by engineer. */ "Sender may have deleted the connection request." = "Il mittente potrebbe aver eliminato la richiesta di connessione."; +/* alert message */ +"Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later." = "L'invio di un'anteprima del link può rivelare il tuo indirizzo IP al sito. Puoi modificarlo nelle impostazioni di Privacy più tardi."; + /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "L'invio delle ricevute di consegna sarà attivo per tutti i contatti in tutti i profili di chat visibili."; @@ -4947,6 +5405,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "info coda server: %1$@\n\nultimo msg ricevuto: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "Il server richiede l'autorizzazione per connettersi al relay, controlla la password."; + /* server test error */ "Server requires authorization to create queues, check password." = "Il server richiede l'autorizzazione di creare code, controlla la password."; @@ -5031,6 +5492,12 @@ chat item action */ /* alert message */ "Settings were changed." = "Le impostazioni sono state cambiate."; +/* No comment provided by engineer. */ +"Setup notifications" = "Configura le notifiche"; + +/* No comment provided by engineer. */ +"Setup routers" = "Configura gli instradatori"; + /* No comment provided by engineer. */ "Shape profile images" = "Forma delle immagini del profilo"; @@ -5051,7 +5518,10 @@ chat item action */ "Share address publicly" = "Condividi indirizzo pubblicamente"; /* alert title */ -"Share address with contacts?" = "Condividere l'indirizzo con i contatti?"; +"Share address with SimpleX contacts?" = "Condividere l'indirizzo con i contatti di SimpleX?"; + +/* No comment provided by engineer. */ +"Share channel" = "Condividi canale"; /* No comment provided by engineer. */ "Share from other apps." = "Condividi da altre app."; @@ -5068,6 +5538,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Condividi il profilo"; +/* No comment provided by engineer. */ +"Share relay address" = "Condividi l'indirizzo del relay"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Condividi l'indirizzo SimpleX sui social media."; @@ -5078,7 +5551,10 @@ chat item action */ "Share to SimpleX" = "Condividi in SimpleX"; /* No comment provided by engineer. */ -"Share with contacts" = "Condividi con i contatti"; +"Share via chat" = "Condividi via chat"; + +/* No comment provided by engineer. */ +"Share with SimpleX contacts" = "Condividi con i contatti di SimpleX"; /* No comment provided by engineer. */ "Share your address" = "Condividi il tuo indirizzo"; @@ -5182,6 +5658,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Protocolli di SimpleX esaminati da Trail of Bits."; +/* simplex link type */ +"SimpleX relay address" = "Indirizzo del relay SimpleX"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modalità incognito semplificata"; @@ -5234,6 +5713,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "crittografia end-to-end standard"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Dai una stella su GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Avvia chat"; @@ -5298,7 +5780,49 @@ report reason */ "Submit" = "Invia"; /* No comment provided by engineer. */ -"Subscribed" = "Iscritto"; +"Subscribed" = "Iscritto/a"; + +/* No comment provided by engineer. */ +"Subscriber" = "Iscritto"; + +/* chat feature */ +"Subscriber reports" = "Segnalazioni degli iscritti"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "L'iscritto verrà rimosso dal canale, non è reversibile!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Iscritti"; + +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Gli iscritti al canale possono aggiungere reazioni ai messaggi."; + +/* No comment provided by engineer. */ +"Subscribers can chat with admins." = "Gli iscritti possono chattare con gli amministratori."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Gli iscritti al canale possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Gli iscritti possono segnalare messaggi ai moderatori."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Gli iscritti al canale possono inviare messaggi diretti."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Gli iscritti al canale possono inviare messaggi a tempo."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Gli iscritti al canale possono inviare file e contenuti multimediali."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Gli iscritti al canale possono inviare link di Simplex."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Gli iscritti al canale possono inviare messaggi vocali."; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Gli iscritti usano il link del relay per connettersi al canale.\nL'indirizzo del relay è stato usato per impostare questo relay per il canale."; /* No comment provided by engineer. */ "Subscription errors" = "Errori di iscrizione"; @@ -5327,6 +5851,9 @@ report reason */ /* No comment provided by engineer. */ "Take picture" = "Scatta foto"; +/* No comment provided by engineer. */ +"Talk to someone" = "Parla con qualcuno"; + /* No comment provided by engineer. */ "Tap button " = "Tocca il pulsante "; @@ -5340,7 +5867,7 @@ report reason */ "Tap Connect to use bot" = "Tocca Connetti per usare il bot"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi."; +"Tap Join channel" = "Tocca Iscriviti al canale"; /* No comment provided by engineer. */ "Tap Join group" = "Tocca Entra nel gruppo"; @@ -5357,6 +5884,9 @@ report reason */ /* No comment provided by engineer. */ "Tap to join incognito" = "Toccare per entrare in incognito"; +/* No comment provided by engineer. */ +"Tap to open" = "Tocca per aprire"; + /* No comment provided by engineer. */ "Tap to paste link" = "Tocca per incollare il link"; @@ -5394,6 +5924,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Prova le notifiche"; +/* No comment provided by engineer. */ +"Test relay" = "Prova relay"; + /* No comment provided by engineer. */ "Test server" = "Prova server"; @@ -5421,6 +5954,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "L'app protegge la tua privacy usando diversi operatori in ogni conversazione."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "L'app ha rimosso questo messaggio dopo %lld tentativi di riceverlo."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "L'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion)."; @@ -5430,6 +5966,9 @@ server test failure */ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Il codice che hai scansionato non è un codice QR di link SimpleX."; +/* conn error description */ +"The connection reached the limit of undelivered messages" = "La connessione ha raggiunto il limite di messaggi non consegnati"; + /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline."; @@ -5446,7 +5985,7 @@ server test failure */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione!"; /* No comment provided by engineer. */ -"The future of messaging" = "La nuova generazione di messaggistica privata"; +"The first network where you own\nyour contacts and groups." = "La prima rete in cui possiedi\ni tuoi contatti e i tuoi gruppi."; /* No comment provided by engineer. */ "The hash of the previous message is different." = "L'hash del messaggio precedente è diverso."; @@ -5472,6 +6011,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "La più antica libertà umana, parlare con un'altra persona senza essere osservati, si basa su un'infrastruttura che non può tradirla."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; @@ -5499,6 +6041,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Temi"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Poi ci siamo trasferiti online e ogni piattaforma ha chiesto un pezzo di noi: il nome, il numero, gli amici. Abbiamo accettato che il prezzo da pagare per comunicare con gli altri fosse quello di far sapere a qualcuno con chi parliamo. Ogni generazione, sia di persone che di tecnologia, ha funzionato così: telefono, email, messenger, social media. Sembrava l'unico modo possibile."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "C'è un'altra via. Una rete senza numeri di telefono. Senza nomi utente. Senza account. Senza identificatori utente di alcun tipo. Una rete che connette le persone e trasferisce messaggi crittografati senza sapere chi è connesso."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Queste condizioni si applicheranno anche per: **%@**."; @@ -5541,6 +6089,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Questo gruppo non esiste più."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Questo è un indirizzo di relay di chat, non può essere usato per connettersi."; + +/* new chat action */ +"This is your link for channel %@!" = "Questo è il tuo link per il canale %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Questo link richiede una versione più recente dell'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile."; @@ -5574,6 +6128,9 @@ server test failure */ /* No comment provided by engineer. */ "To make a new connection" = "Per creare una nuova connessione"; +/* No comment provided by engineer. */ +"To make SimpleX Network last." = "Per la sostenibilità della rete di SimpleX."; + /* No comment provided by engineer. */ "To protect against your link being replaced, you can compare contact security codes." = "Per proteggerti dalla sostituzione del tuo link, puoi confrontare i codici di sicurezza del contatto."; @@ -5622,9 +6179,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Cambia l'elenco delle chat:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Attiva/disattiva l'incognito quando ti colleghi."; @@ -5634,6 +6188,9 @@ server test failure */ /* No comment provided by engineer. */ "Toolbar opacity" = "Opacità barra degli strumenti"; +/* No comment provided by engineer. */ +"Top bar" = "Barra superiore"; + /* No comment provided by engineer. */ "Total" = "Totale"; @@ -5673,6 +6230,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Sbloccare il membro?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Sbloccare l'iscritto per tutti?"; + /* rcv group event chat item */ "unblocked %@" = "ha sbloccato %@"; @@ -5745,12 +6305,15 @@ server test failure */ /* swipe action */ "Unread" = "Non letto"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Link di connessione non supportato"; /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Vengono inviati ai nuovi membri fino a 100 ultimi messaggi."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Vengono inviati ai nuovi iscritti fino a 100 ultimi messaggi."; + /* No comment provided by engineer. */ "Update" = "Aggiorna"; @@ -5763,6 +6326,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Aggiornare le impostazioni?"; +/* rcv group event chat item */ +"updated channel profile" = "profilo del canale aggiornato"; + /* No comment provided by engineer. */ "Updated conditions" = "Condizioni aggiornate"; @@ -5820,9 +6386,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Usa %@"; -/* No comment provided by engineer. */ -"Use chat" = "Usa la chat"; - /* new chat action */ "Use current profile" = "Usa il profilo attuale"; @@ -5832,6 +6395,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Usa per i messaggi"; +/* No comment provided by engineer. */ +"Use for new channels" = "Usa per canali nuovi"; + /* No comment provided by engineer. */ "Use for new connections" = "Usa per connessioni nuove"; @@ -5856,6 +6422,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Usa l'instradamento privato con server sconosciuti."; +/* No comment provided by engineer. */ +"Use relay" = "Usa relay"; + /* No comment provided by engineer. */ "Use server" = "Usa il server"; @@ -5880,6 +6449,9 @@ server test failure */ /* No comment provided by engineer. */ "Use the app with one hand." = "Usa l'app con una mano sola."; +/* No comment provided by engineer. */ +"Use this address in your social media profile, website, or email signature." = "Usa questo indirizzo nel tuo profilo di social media, sito web o firma email."; + /* No comment provided by engineer. */ "Use web port" = "Usa porta web"; @@ -5898,6 +6470,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Verifica"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Verifica il codice con il desktop"; @@ -5919,6 +6494,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Verifica codice di sicurezza"; +/* relay hostname */ +"via %@" = "via %@"; + /* No comment provided by engineer. */ "Via browser" = "Via browser"; @@ -5988,9 +6566,18 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "Messaggi vocali vietati!"; +/* alert action */ +"Wait" = "Attendi"; + +/* relay test step */ +"Wait response" = "Attendi risposta"; + /* No comment provided by engineer. */ "waiting for answer…" = "in attesa di risposta…"; +/* No comment provided by engineer. */ +"Waiting for channel owner to add relays." = "In attesa che il proprietario del canale aggiunga dei relay."; + /* No comment provided by engineer. */ "waiting for confirmation…" = "in attesa di conferma…"; @@ -6021,6 +6608,9 @@ server test failure */ /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Attenzione: potresti perdere alcuni dati!"; +/* No comment provided by engineer. */ +"We made connecting simpler for new users." = "Abbiamo semplificato la connessione per i nuovi utenti."; + /* No comment provided by engineer. */ "WebRTC ICE servers" = "Server WebRTC ICE"; @@ -6057,6 +6647,9 @@ server test failure */ /* 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." = "Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano."; +/* No comment provided by engineer. */ +"Why SimpleX is built." = "Perché costruiamo SimpleX."; + /* No comment provided by engineer. */ "WiFi" = "WiFi"; @@ -6156,6 +6749,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "sei un osservatore"; +/* No comment provided by engineer. */ +"you are subscriber" = "sei iscritto/a"; + /* snd group event chat item */ "you blocked %@" = "hai bloccato %@"; @@ -6198,6 +6794,9 @@ server test failure */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puoi impostare l'anteprima della notifica nella schermata di blocco tramite le impostazioni."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the channel." = "Puoi condividere un link o un codice QR, chiunque sarà in grado di iscriversi al canale."; + /* No comment provided by engineer. */ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puoi condividere un link o un codice QR: chiunque potrà unirsi al gruppo. Non perderai i membri del gruppo se in seguito lo elimini."; @@ -6238,10 +6837,13 @@ server test failure */ "you changed role of %@ to %@" = "hai cambiato il ruolo di %1$@ in %2$@"; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Non è stato possibile verificarti, riprova."; +"You commit to:\n- Only legal content in public groups\n- Respect other users - no spam" = "Tu ti impegni a:\n- Pubblicare solo contenuto legale nei gruppi pubblici\n- Rispettare gli altri utenti. Niente spam"; /* No comment provided by engineer. */ -"You decide who can connect." = "Sei tu a decidere chi può connettersi."; +"You connected to the channel via this relay link." = "Ti sei connesso/a al canale attraverso questo link del relay."; + +/* No comment provided by engineer. */ +"You could not be verified; please try again." = "Non è stato possibile verificarti, riprova."; /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Hai già richiesto la connessione!\nRipetere la richiesta di connessione?"; @@ -6297,6 +6899,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "hai sbloccato %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Sei nato senza un account"; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Potrai inviare messaggi **solo dopo che la tua richiesta verrà accettata**."; @@ -6318,6 +6923,9 @@ server test failure */ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Smetterai di ricevere messaggi da questo canale. La cronologia della chat sarà preservata."; + /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Non riceverai più messaggi da questa chat. La cronologia della chat verrà conservata."; @@ -6342,6 +6950,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Le tue chiamate"; +/* No comment provided by engineer. */ +"Your channel" = "Il tuo canale"; + /* No comment provided by engineer. */ "Your chat database" = "Il tuo database della chat"; @@ -6372,6 +6983,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "I tuoi contatti resteranno connessi."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Le tue conversazioni appartengono a te, come è sempre stato prima dell'avvento di internet. La rete non è un luogo che visiti. È un luogo che crei e possiedi. E nessuno può portartelo via, che tu lo renda privato o pubblico."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Le credenziali potrebbero essere inviate in chiaro."; @@ -6387,6 +7001,9 @@ server test failure */ /* No comment provided by engineer. */ "Your ICE servers" = "I tuoi server ICE"; +/* No comment provided by engineer. */ +"Your network" = "La tua rete"; + /* No comment provided by engineer. */ "Your preferences" = "Le tue preferenze"; @@ -6396,6 +7013,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Il tuo profilo"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "Il tuo profilo **%@** verrà condiviso con i relay e gli iscritti.\nI relay hanno accesso ai messaggi del canale."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**."; @@ -6408,9 +7028,18 @@ server test failure */ /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti."; +/* No comment provided by engineer. */ +"Your public address" = "Il tuo indirizzo pubblico"; + /* No comment provided by engineer. */ "Your random profile" = "Il tuo profilo casuale"; +/* No comment provided by engineer. */ +"Your relay address" = "L'indirizzo del tuo relay"; + +/* No comment provided by engineer. */ +"Your relay name" = "Il nome del tuo relay"; + /* No comment provided by engineer. */ "Your server address" = "L'indirizzo del tuo server"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 38f90a2cad..b14777244f 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(このデバイス v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[貢献する](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[メールを送信](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[GitHub でスターを付ける](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。"; @@ -377,9 +371,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "アクティブな接続"; -/* 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 friends" = "友達を追加"; @@ -566,9 +557,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "通話に応答"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。"; - /* No comment provided by engineer. */ "App build: %@" = "アプリのビルド: %@"; @@ -969,7 +957,7 @@ server test step */ /* alert title */ "Connection error" = "接続エラー"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "接続エラー (AUTH)"; /* chat list item title (it should not be shown */ @@ -1020,15 +1008,15 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "続ける"; +/* No comment provided by engineer. */ +"Contribute" = "貢献する"; + /* No comment provided by engineer. */ "Copy" = "コピー"; /* No comment provided by engineer. */ "Core version: v%@" = "コアのバージョン: v%@"; -/* No comment provided by engineer. */ -"Create" = "作成"; - /* server test step */ "Create file" = "ファイルを作成"; @@ -1143,9 +1131,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "配信のデバッグ"; -/* No comment provided by engineer. */ -"Decentralized" = "分散型"; - /* message decrypt error item */ "Decryption error" = "復号化エラー"; @@ -1386,7 +1371,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "グループのプロフィールを編集"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "有効"; /* No comment provided by engineer. */ @@ -1404,9 +1389,6 @@ alert button */ /* No comment provided by engineer. */ "Enable lock" = "ロックモード"; -/* No comment provided by engineer. */ -"Enable notifications" = "通知を有効化"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "定期的な通知を有効にしますか?"; @@ -1518,7 +1500,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "エラー"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "エラー"; /* No comment provided by engineer. */ @@ -1788,7 +1770,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "グループ招待が無効となり、送信元によって取り消されました。"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "グループのリンク"; /* No comment provided by engineer. */ @@ -1890,9 +1872,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "即座に"; -/* No comment provided by engineer. */ -"Immune to spam" = "スパムや悪質送信を防止"; - /* No comment provided by engineer. */ "Import" = "読み込む"; @@ -1957,7 +1936,7 @@ server test error */ "Initial role" = "初期の役割"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "インストール ターミナル用SimpleX Chat"; /* No comment provided by engineer. */ "Instant" = "即時"; @@ -1974,7 +1953,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "無効なチャットデータ"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "無効な接続リンク"; /* invalid chat item */ @@ -2217,9 +2196,6 @@ server test error */ /* No comment provided by engineer. */ "Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。"; -/* No comment provided by engineer. */ -"Migrate from another device" = "別の端末から移行"; - /* No comment provided by engineer. */ "Migrating database archive…" = "データベースのアーカイブを移行しています…"; @@ -2364,9 +2340,6 @@ server test error */ /* copied message info in history */ "no text" = "テキストなし"; -/* No comment provided by engineer. */ -"No user identifiers." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。"; - /* No comment provided by engineer. */ "Notifications" = "通知"; @@ -2459,7 +2432,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "音声メッセージを送れるのはあなたの連絡相手だけです。"; -/* alert action */ +/* alert action +alert button */ "Open" = "開く"; /* new chat action */ @@ -2561,9 +2535,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "プライバシーとセキュリティ"; -/* No comment provided by engineer. */ -"Privacy redefined" = "プライバシーの基準を新境地に"; - /* No comment provided by engineer. */ "Private filenames" = "プライベートなファイル名"; @@ -2579,9 +2550,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "プロフィールのパスワード"; -/* alert message */ -"Profile update will be sent to your contacts." = "連絡先にプロフィール更新のお知らせが届きます。"; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "音声/ビデオ通話を禁止する 。"; @@ -2634,13 +2602,10 @@ new chat action */ "Read more" = "続きを読む"; /* No comment provided by engineer. */ -"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)をご覧ください。"; +"Read more in our GitHub repository." = "詳しくはGitHubリポジトリをご覧ください。"; /* 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)をご覧ください。"; - -/* 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)をご覧ください。"; +"Read more in User Guide." = "詳しくはユーザーガイドをご覧ください。"; /* No comment provided by engineer. */ "received answer…" = "回答を受け取りました…"; @@ -2979,15 +2944,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "アドレスを共有する"; -/* alert title */ -"Share address with contacts?" = "アドレスを連絡先と共有しますか?"; - /* No comment provided by engineer. */ "Share link" = "リンクを送る"; -/* No comment provided by engineer. */ -"Share with contacts" = "連絡先と共有する"; - /* No comment provided by engineer. */ "Show calls in phone history" = "通話履歴を表示"; @@ -3057,6 +3016,9 @@ chat item action */ /* notification title */ "Somebody" = "誰か"; +/* No comment provided by engineer. */ +"Star on GitHub" = "GitHub でスターを付ける"; + /* No comment provided by engineer. */ "Start chat" = "チャットを開始する"; @@ -3175,9 +3137,6 @@ server test failure */ /* 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." = "以前のメッセージとハッシュ値が異なります。"; @@ -3340,9 +3299,6 @@ server test failure */ /* No comment provided by engineer. */ "Use .onion hosts" = ".onionホストを使う"; -/* No comment provided by engineer. */ -"Use chat" = "チャット"; - /* new chat action */ "Use current profile" = "現在のプロファイルを使用する"; @@ -3550,9 +3506,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "確認できませんでした。 もう一度お試しください。"; -/* No comment provided by engineer. */ -"You decide who can connect." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。"; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index d4ace22665..e12e255488 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(dit apparaat v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Bijdragen](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Stuur ons een e-mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen."; @@ -395,9 +389,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Actieve verbindingen"; -/* 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." = "Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden."; - /* No comment provided by engineer. */ "Add friends" = "Vrienden toevoegen"; @@ -635,9 +626,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Beantwoord oproep"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Iedereen kan servers hosten."; - /* No comment provided by engineer. */ "App build: %@" = "App build: %@"; @@ -867,7 +855,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaars, Fins, Thais en Oekraïens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Zakelijk adres"; /* No comment provided by engineer. */ @@ -879,9 +867,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Door SimpleX Chat te gebruiken, gaat u ermee akkoord:\n- alleen legale content te versturen in openbare groepen.\n- andere gebruikers te respecteren – geen spam."; - /* No comment provided by engineer. */ "call" = "bellen"; @@ -1062,7 +1047,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chat met beheerders"; /* No comment provided by engineer. */ @@ -1173,9 +1159,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE servers configureren"; -/* No comment provided by engineer. */ -"Configure server operators" = "Serveroperators configureren"; - /* No comment provided by engineer. */ "Confirm" = "Bevestigen"; @@ -1306,7 +1289,7 @@ server test step */ /* alert title */ "Connection error" = "Verbindingsfout"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Verbindingsfout (AUTH)"; /* chat list item title (it should not be shown */ @@ -1402,6 +1385,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Doorgaan"; +/* No comment provided by engineer. */ +"Contribute" = "Bijdragen"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Gesprek verwijderd!"; @@ -1420,9 +1406,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Juiste naam voor %@?"; -/* No comment provided by engineer. */ -"Create" = "Maak"; - /* No comment provided by engineer. */ "Create 1-time link" = "Eenmalige link maken"; @@ -1573,9 +1556,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Foutopsporing bezorging"; -/* No comment provided by engineer. */ -"Decentralized" = "Gedecentraliseerd"; - /* message decrypt error item */ "Decryption error" = "Decodering fout"; @@ -1964,7 +1944,7 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Groep profiel bewerken"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Inschakelen"; /* No comment provided by engineer. */ @@ -1991,9 +1971,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "Vergrendeling inschakelen"; -/* No comment provided by engineer. */ -"Enable notifications" = "Meldingen aanzetten"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Periodieke meldingen inschakelen?"; @@ -2135,7 +2112,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "fout"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Fout"; /* No comment provided by engineer. */ @@ -2655,7 +2632,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "groep is verwijderd"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Groep link"; /* No comment provided by engineer. */ @@ -2778,9 +2755,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Onmiddellijk"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immuun voor spam en misbruik"; - /* No comment provided by engineer. */ "Import" = "Importeren"; @@ -2881,7 +2855,7 @@ servers warning */ "Initial role" = "Initiële rol"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installeer SimpleX Chat voor terminal"; /* No comment provided by engineer. */ "Instant" = "Direct"; @@ -2916,7 +2890,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "ongeldige gesprek gegevens"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Ongeldige verbinding link"; /* invalid chat item */ @@ -3330,9 +3304,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Apparaat migreren"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Migreer vanaf een ander apparaat"; - /* No comment provided by engineer. */ "Migrate here" = "Migreer hierheen"; @@ -3603,9 +3574,6 @@ servers warning */ /* No comment provided by engineer. */ "No unread chats" = "Geen ongelezen chats"; -/* No comment provided by engineer. */ -"No user identifiers." = "Geen gebruikers-ID's."; - /* No comment provided by engineer. */ "Not compatible!" = "Niet compatibel!"; @@ -3662,7 +3630,7 @@ alert button new chat action */ "Ok" = "OK"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3737,7 +3705,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Alleen uw contact kan spraak berichten verzenden."; -/* alert action */ +/* alert action +alert button */ "Open" = "Open"; /* No comment provided by engineer. */ @@ -3968,12 +3937,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Privacybeleid en gebruiksvoorwaarden."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Privacy opnieuw gedefinieerd"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders."; - /* No comment provided by engineer. */ "Private filenames" = "Privé bestandsnamen"; @@ -4010,9 +3973,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Profiel thema"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profiel update wordt naar uw contacten verzonden."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Audio/video gesprekken verbieden."; @@ -4098,16 +4058,10 @@ new chat action */ "Read more" = "Lees meer"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Lees meer in onze GitHub-repository."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Lees meer in de Gebruikershandleiding."; /* No comment provided by engineer. */ "Receipts are disabled" = "Bevestigingen zijn uitgeschakeld"; @@ -4797,9 +4751,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Adres openbaar delen"; -/* alert title */ -"Share address with contacts?" = "Adres delen met contacten?"; - /* No comment provided by engineer. */ "Share from other apps." = "Delen vanuit andere apps."; @@ -4818,9 +4769,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Delen op SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Delen met contacten"; - /* No comment provided by engineer. */ "Short link" = "Korte link"; @@ -4966,6 +4914,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "standaard end-to-end encryptie"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Star on GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Begin gesprek"; @@ -5062,9 +5013,6 @@ report reason */ /* No comment provided by engineer. */ "Tap button " = "Tik op de knop "; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tik op SimpleX-adres maken in het menu om het later te maken."; - /* No comment provided by engineer. */ "Tap to activate profile." = "Tik hier om profiel te activeren."; @@ -5159,9 +5107,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten!"; -/* No comment provided by engineer. */ -"The future of messaging" = "De volgende generatie privéberichten"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "De hash van het vorige bericht is anders."; @@ -5321,9 +5266,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contact te verifiëren."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Chatlijst wisselen:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Schakel incognito in tijdens het verbinden."; @@ -5441,7 +5383,7 @@ server test failure */ /* swipe action */ "Unread" = "Ongelezen"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Niet-ondersteunde verbindingslink"; /* No comment provided by engineer. */ @@ -5498,9 +5440,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Gebruik %@"; -/* No comment provided by engineer. */ -"Use chat" = "Gebruik chat"; - /* new chat action */ "Use current profile" = "Gebruik het huidige profiel"; @@ -5903,9 +5842,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "U kon niet worden geverifieerd; probeer het opnieuw."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Jij bepaalt wie er verbinding mag maken."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Je hebt al verbinding aangevraagd!\nVerbindingsverzoek herhalen?"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 7c28c9591c..e2e46590c9 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(to urządzenie v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Przyczyń się](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Wyślij do nas email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku."; @@ -398,9 +392,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Aktywne połączenia"; -/* 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." = "Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; - /* No comment provided by engineer. */ "Add friends" = "Dodaj znajomych"; @@ -650,9 +641,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Odbierz połączenie"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Każdy może hostować serwery."; - /* No comment provided by engineer. */ "App build: %@" = "Kompilacja aplikacji: %@"; @@ -794,6 +782,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Zły identyfikator wiadomości"; +/* No comment provided by engineer. */ +"Be free in your network." = "Ciesz się swobodą w swojej sieci."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana."; + /* No comment provided by engineer. */ "Better calls" = "Lepsze połączenia"; @@ -897,7 +891,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bułgarski, fiński, tajski i ukraiński – dzięki użytkownikom i [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Adres firmowy"; /* No comment provided by engineer. */ @@ -912,9 +906,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Korzystając z SimpleX Chat, zgadzasz się:\n- wysyłać tylko legalne treści w grupach publicznych.\n- szanować innych użytkowników – nie spamować."; - /* No comment provided by engineer. */ "call" = "zadzwoń"; @@ -1098,7 +1089,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Czat zostanie usunięty dla Ciebie – tej operacji nie można cofnąć!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Czatuj z administratorami"; /* No comment provided by engineer. */ @@ -1212,9 +1204,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Skonfiguruj serwery ICE"; -/* No comment provided by engineer. */ -"Configure server operators" = "Skonfiguruj operatorów serwerów"; - /* No comment provided by engineer. */ "Confirm" = "Potwierdź"; @@ -1348,7 +1337,7 @@ server test step */ /* alert title */ "Connection error" = "Błąd połączenia"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Błąd połączenia (UWIERZYTELNIANIE)"; /* chat list item title (it should not be shown */ @@ -1453,6 +1442,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Kontynuuj"; +/* No comment provided by engineer. */ +"Contribute" = "Przyczyń się"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Rozmowa usunięta!"; @@ -1471,9 +1463,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Poprawić imię na %@?"; -/* No comment provided by engineer. */ -"Create" = "Utwórz"; - /* No comment provided by engineer. */ "Create 1-time link" = "Utwórz jednorazowy link"; @@ -1627,9 +1616,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Dostarczenie debugowania"; -/* No comment provided by engineer. */ -"Decentralized" = "Zdecentralizowane"; - /* message decrypt error item */ "Decryption error" = "Błąd odszyfrowania"; @@ -2033,7 +2019,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Pusta wiadomość!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Włącz"; /* No comment provided by engineer. */ @@ -2063,9 +2049,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "Włącz blokadę"; -/* No comment provided by engineer. */ -"Enable notifications" = "Włącz powiadomienia"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Włączyć okresowe powiadomienia?"; @@ -2207,7 +2190,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "błąd"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Błąd"; /* No comment provided by engineer. */ @@ -2767,7 +2750,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "grupa została usunięta"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Link do grupy"; /* No comment provided by engineer. */ @@ -2899,9 +2882,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Natychmiast"; -/* No comment provided by engineer. */ -"Immune to spam" = "Odporność na spam i nadużycia"; - /* No comment provided by engineer. */ "Import" = "Importuj"; @@ -3002,7 +2982,7 @@ servers warning */ "Initial role" = "Rola początkowa"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Zainstaluj SimpleX Chat na terminal"; /* No comment provided by engineer. */ "Instant" = "Natychmiastowo"; @@ -3037,7 +3017,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "nieprawidłowe dane czatu"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Nieprawidłowy link połączenia"; /* invalid chat item */ @@ -3481,9 +3461,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Zmigruj urządzenie"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Zmigruj z innego urządzenia"; - /* No comment provided by engineer. */ "Migrate here" = "Zmigruj tutaj"; @@ -3764,7 +3741,10 @@ servers warning */ "No unread chats" = "Brak nieprzeczytanych czatów"; /* No comment provided by engineer. */ -"No user identifiers." = "Brak identyfikatorów użytkownika."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem."; /* No comment provided by engineer. */ "Not compatible!" = "Nie kompatybilny!"; @@ -3822,7 +3802,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3903,7 +3883,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Tylko Twój kontakt może wysyłać wiadomości głosowe."; -/* alert action */ +/* alert action +alert button */ "Open" = "Otwórz"; /* No comment provided by engineer. */ @@ -4158,12 +4139,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Polityka prywatności i warunki korzystania."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Redefinicja prywatności"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów."; - /* No comment provided by engineer. */ "Private filenames" = "Prywatne nazwy plików"; @@ -4203,9 +4178,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Motyw profilu"; -/* alert message */ -"Profile update will be sent to your contacts." = "Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Zabroń połączeń audio/wideo."; @@ -4294,16 +4266,10 @@ new chat action */ "Read more" = "Przeczytaj więcej"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Przeczytaj więcej na naszym repozytorium GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Przeczytaj więcej w [Podręczniku Użytkownika](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)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Przeczytaj więcej w Poradniku Użytkownika."; /* No comment provided by engineer. */ "Receipts are disabled" = "Potwierdzenia są wyłączone"; @@ -5050,9 +5016,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Udostępnij adres publicznie"; -/* alert title */ -"Share address with contacts?" = "Udostępnić adres kontaktom?"; - /* No comment provided by engineer. */ "Share from other apps." = "Udostępnij z innych aplikacji."; @@ -5077,9 +5040,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Udostępnij do SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Udostępnij kontaktom"; - /* No comment provided by engineer. */ "Share your address" = "Udostępnij swój adres"; @@ -5234,6 +5194,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "standardowe szyfrowanie end-to-end"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Daj gwiazdkę na GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Rozpocznij czat"; @@ -5339,9 +5302,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "Dotknij Połącz aby użyć bota"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Dotknij Stwórz adres SimpleX w menu aby utworzyć go później."; - /* No comment provided by engineer. */ "Tap Join group" = "Dotknij Dołącz do grupy"; @@ -5445,9 +5405,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Następna generacja prywatnych wiadomości"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Hash poprzedniej wiadomości jest inny."; @@ -5472,6 +5429,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Te same warunki będą miały zastosowanie do operatora **%@**."; @@ -5499,6 +5459,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Motywy"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Warunki te będą miały również zastosowanie w przypadku: **%@**."; @@ -5622,9 +5588,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Przełącz listę czatów:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Przełącz incognito przy połączeniu."; @@ -5745,7 +5708,7 @@ server test failure */ /* swipe action */ "Unread" = "Nieprzeczytane"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Nieobsługiwane łącze połączenia"; /* No comment provided by engineer. */ @@ -5820,9 +5783,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Użyj %@"; -/* No comment provided by engineer. */ -"Use chat" = "Użyj czatu"; - /* new chat action */ "Use current profile" = "Użyj obecnego profilu"; @@ -6240,9 +6200,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nie można zweryfikować użytkownika; proszę spróbować ponownie."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ty decydujesz, kto może się połączyć."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Już prosiłeś o połączenie!\nPowtórzyć prośbę połączenia?"; @@ -6297,6 +6254,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "odblokowałeś %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Urodziłeś się bez konta."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Będziesz mógł wysyłać wiadomości **dopiero po zaakceptowaniu Twojej prośby**."; @@ -6372,6 +6332,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Twoje kontakty pozostaną połączone."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Twoje poświadczenia mogą zostać wysłane niezaszyfrowane."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index eee33cc574..c2b01228a2 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -5,11 +5,14 @@ "_italic_" = "\\_курсив_"; /* No comment provided by engineer. */ -"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- соединиться с [каталогом групп](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- отчеты о доставке (до 20 членов).\n- быстрее и стабильнее."; +"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- соединиться с [каталогом групп](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- отчёты о доставке (до 20 членов).\n- быстрее и стабильнее."; /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- более стабильная доставка сообщений.\n- немного улучшенные группы.\n- и прочее!"; +/* No comment provided by engineer. */ +"- opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking." = "- включение картинок ссылок.\n- защита от фишинга.\n- удаление трекинга ссылок."; + /* No comment provided by engineer. */ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- опционально уведомляйте удалённые контакты.\n- имена профилей с пробелами.\n- и прочее!"; @@ -19,21 +22,21 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 цвет!"; +/* chat link info line */ +"(from owner)" = "(от владельца)"; + /* No comment provided by engineer. */ "(new)" = "(новое)"; +/* chat link info line */ +"(signed)" = "(с подписью)"; + /* No comment provided by engineer. */ "(this device v%@)" = "(это устройство v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внести свой вклад](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Отправить email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Поставить звездочку в GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Добавить контакт**: создать и поделиться новой ссылкой-приглашением."; @@ -56,7 +59,7 @@ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Обратите внимание**: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений."; /* No comment provided by engineer. */ -"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете."; +"**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 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, но сервер не получает сами сообщения, их размер или от кого они."; @@ -65,10 +68,13 @@ "**Scan / Paste link**: to connect via a link you received." = "**Сканировать / Вставить ссылку**: чтобы соединиться через полученную ссылку."; /* No comment provided by engineer. */ -"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain."; +"**Test relay** to retrieve its name." = "**Протестируйте релей**, чтобы получить его имя."; /* No comment provided by engineer. */ -"**Warning**: the archive will be removed." = "**Внимание**: архив будет удален."; +"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранён в Keychain."; + +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Внимание**: архив будет удалён."; /* No comment provided by engineer. */ "*bold*" = "\\*жирный*"; @@ -164,7 +170,7 @@ "%d file(s) were not downloaded." = "%d файлов не было загружено."; /* time interval */ -"%d hours" = "%d ч."; +"%d hours" = "%d ч"; /* alert title */ "%d messages not forwarded" = "%d сообщений не переслано"; @@ -173,7 +179,19 @@ "%d min" = "%d мин"; /* time interval */ -"%d months" = "%d мес."; +"%d months" = "%d мес"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays failed" = "%d релеев с ошибками"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays not active" = "%d релеев неактивны"; + +/* channel relay bar +channel subscriber relay bar */ +"%d relays removed" = "%d релеев удалены"; /* time interval */ "%d sec" = "%d сек"; @@ -184,15 +202,50 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d пропущенных сообщение(й)"; +/* channel subscriber count */ +"%d subscriber" = "%d подписчик"; + +/* channel subscriber count */ +"%d subscribers" = "%d подписчиков"; + /* time interval */ "%d weeks" = "%d недель"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d релеев активны"; + +/* channel relay bar */ +"%d/%d relays active, %d errors" = "%1$d/%2$d релеев активны, %3$d с ошибками"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d релеев активны, %3$d с ошибками"; + +/* channel relay bar */ +"%d/%d relays active, %d removed" = "%1$d/%2$d релеев активны, %3$d удалены"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d релеев подключены"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d релеев подключены, %3$d с ошибками"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d failed" = "%1$d/%2$d релеев подключены, %3$d с ошибками"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d removed" = "%1$d/%2$d релеев подключены, %3$d удалены"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld событий канала"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "Выбрано контактов: %lld"; @@ -209,7 +262,7 @@ "%lld messages blocked" = "%lld сообщений заблокировано"; /* No comment provided by engineer. */ -"%lld messages blocked by admin" = "%lld сообщений заблокировано администратором"; +"%lld messages blocked by admin" = "%lld сообщений заблокировано админом"; /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld сообщений помечено удалёнными"; @@ -218,7 +271,7 @@ "%lld messages moderated by %@" = "%lld сообщений модерировано членом %@"; /* No comment provided by engineer. */ -"%lld minutes" = "%lld минуты"; +"%lld minutes" = "%lld минут(ы)"; /* No comment provided by engineer. */ "%lld new interface languages" = "%lld новых языков интерфейса"; @@ -262,6 +315,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~зачеркнуть~"; +/* owner verification */ +"⚠️ Signature verification failed: %@." = "⚠️ Ошибка проверки подписи: %@."; + /* time to disappear */ "0 sec" = "0 сек"; @@ -305,7 +361,10 @@ time interval */ "30 seconds" = "30 секунд"; /* No comment provided by engineer. */ -"A few more things" = "Еще несколько изменений"; +"A few more things" = "Ещё несколько изменений"; + +/* No comment provided by engineer. */ +"A link for one person to connect" = "Ссылка для одного человека"; /* notification title */ "A new contact" = "Новый контакт"; @@ -317,7 +376,7 @@ time interval */ "A separate TCP connection will be used **for each chat profile you have in the app**." = "Отдельное TCP-соединение будет использоваться **для каждого профиля чата, который Вы имеете в приложении**."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "Будет использовано отдельное TCP соединение **для каждого контакта и члена группы**.\n**Примечание**: Чем больше подключений, тем быстрее разряжается батарея и расходуется трафик, а некоторые соединения могут отваливаться."; +"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "Будет использовано отдельное TCP-соединение **для каждого контакта и члена группы**.\n**Примечание**: Чем больше подключений, тем быстрее разряжается батарея и расходуется трафик, а некоторые соединения могут отваливаться."; /* No comment provided by engineer. */ "Abort" = "Прекратить"; @@ -369,7 +428,10 @@ swipe action */ "Accept incognito" = "Принять инкогнито"; /* alert title */ -"Accept member" = "Принять члена"; +"Accept member" = "Принять члена группы"; + +/* No comment provided by engineer. */ +"accepted" = "принят(а)"; /* rcv group event chat item */ "accepted %@" = "принят %@"; @@ -392,6 +454,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Ошибки подтверждения"; +/* No comment provided by engineer. */ +"active" = "активный"; + /* token status text */ "Active" = "Активный"; @@ -399,7 +464,7 @@ swipe action */ "Active connections" = "Активные соединения"; /* 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." = "Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам."; +"Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts." = "Добавьте адрес в свой профиль, чтобы Ваши SimpleX контакты могли поделиться им. Профиль будет отправлен Вашим SimpleX контактам."; /* No comment provided by engineer. */ "Add friends" = "Добавить друзей"; @@ -408,7 +473,7 @@ swipe action */ "Add list" = "Добавить список"; /* placeholder for sending contact request */ -"Add message" = "Добавить cообщение"; +"Add message" = "Добавить сообщение"; /* No comment provided by engineer. */ "Add profile" = "Добавить профиль"; @@ -417,7 +482,7 @@ swipe action */ "Add server" = "Добавить сервер"; /* No comment provided by engineer. */ -"Add servers by scanning QR codes." = "Добавить серверы через QR код."; +"Add servers by scanning QR codes." = "Добавить серверы через QR-код."; /* No comment provided by engineer. */ "Add team members" = "Добавить сотрудников"; @@ -440,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "Added message servers" = "Дополнительные серверы сообщений"; +/* No comment provided by engineer. */ +"Adding relays will be supported later." = "Добавление релеев будет поддерживаться позже."; + /* No comment provided by engineer. */ "Additional accent" = "Дополнительный акцент"; @@ -477,7 +545,7 @@ swipe action */ "Advanced network settings" = "Настройки сети"; /* No comment provided by engineer. */ -"Advanced settings" = "Настройки сети"; +"Advanced settings" = "Дополнительные настройки"; /* chat item text */ "agreeing encryption for %@…" = "шифрование согласовывается для %@…"; @@ -498,7 +566,7 @@ swipe action */ "All chats and messages will be deleted - this cannot be undone!" = "Все чаты и сообщения будут удалены - это нельзя отменить!"; /* alert message */ -"All chats will be removed from the list %@, and the list deleted." = "Все чаты будут удалены из списка %@, и список удален."; +"All chats will be removed from the list %@, and the list deleted." = "Все чаты будут удалены из списка %@, и список удалён."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Все данные удаляются при его вводе."; @@ -513,7 +581,10 @@ swipe action */ "all members" = "все члены"; /* No comment provided by engineer. */ -"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; +"All messages" = "Все сообщения"; + +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **сквозным шифрованием**, с пост-квантовой безопасностью в прямых разговорах."; /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Все сообщения будут удалены - это нельзя отменить!"; @@ -527,6 +598,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Все профили"; +/* No comment provided by engineer. */ +"All relays failed" = "Все релеи недоступны"; + +/* No comment provided by engineer. */ +"All relays removed" = "Все релеи удалены"; + /* No comment provided by engineer. */ "All reports will be archived for you." = "Все сообщения о нарушениях будут заархивированы для вас."; @@ -537,10 +614,10 @@ swipe action */ "All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся."; /* No comment provided by engineer. */ -"All your contacts will remain connected. Profile update will be sent to your contacts." = "Все Ваши контакты сохранятся. Обновленный профиль будет отправлен Вашим контактам."; +"All your contacts will remain connected. Profile update will be sent to your contacts." = "Все Ваши контакты сохранятся. Обновлённый профиль будет отправлен Вашим контактам."; /* No comment provided by engineer. */ -"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы."; +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Все ваши контакты, разговоры и файлы будут надёжно зашифрованы и загружены на выбранные XFTP-серверы."; /* No comment provided by engineer. */ "Allow" = "Разрешить"; @@ -563,6 +640,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа)"; +/* No comment provided by engineer. */ +"Allow members to chat with admins." = "Разрешить членам группы общаться с админами."; + /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Разрешить реакции на сообщения, только если ваш контакт разрешает их."; @@ -572,12 +652,18 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Разрешить личные сообщения членам группы."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Разрешить отправку личных сообщений подписчикам."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Разрешить посылать исчезающие сообщения."; /* No comment provided by engineer. */ "Allow sharing" = "Разрешить поделиться"; +/* No comment provided by engineer. */ +"Allow subscribers to chat with admins." = "Разрешить подписчикам общаться с админами."; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Разрешить необратимо удалять отправленные сообщения. (24 часа)"; @@ -633,7 +719,7 @@ swipe action */ "Always use private routing." = "Всегда использовать конфиденциальную доставку."; /* No comment provided by engineer. */ -"Always use relay" = "Всегда соединяться через relay"; +"Always use relay" = "Всегда соединяться через релей"; /* No comment provided by engineer. */ "An empty chat profile with the provided name is created, and the app opens as usual." = "Будет создан пустой профиль чата с указанным именем, и приложение откроется в обычном режиме."; @@ -647,9 +733,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Принять звонок"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Кто угодно может запустить сервер."; - /* No comment provided by engineer. */ "App build: %@" = "Сборка приложения: %@"; @@ -669,7 +752,7 @@ swipe action */ "App passcode" = "Код доступа в приложение"; /* No comment provided by engineer. */ -"App passcode is replaced with self-destruct passcode." = "Код доступа в приложение будет заменен кодом самоуничтожения."; +"App passcode is replaced with self-destruct passcode." = "Код доступа в приложение будет заменён кодом самоуничтожения."; /* No comment provided by engineer. */ "App session" = "Сессия приложения"; @@ -734,6 +817,9 @@ swipe action */ /* No comment provided by engineer. */ "Audio and video calls" = "Аудио и видео звонки"; +/* No comment provided by engineer. */ +"Audio call" = "Аудиозвонок"; + /* No comment provided by engineer. */ "audio call (not e2e encrypted)" = "аудиозвонок (не e2e зашифрованный)"; @@ -759,13 +845,13 @@ swipe action */ "author" = "автор"; /* No comment provided by engineer. */ -"Auto-accept" = "Автоприем"; +"Auto-accept" = "Автоприём"; /* No comment provided by engineer. */ "Auto-accept contact requests" = "Автоматически принимать запросы контактов"; /* No comment provided by engineer. */ -"Auto-accept images" = "Автоприем изображений"; +"Auto-accept images" = "Автоприём изображений"; /* No comment provided by engineer. */ "Back" = "Назад"; @@ -777,10 +863,10 @@ swipe action */ "Bad desktop address" = "Неверный адрес компьютера"; /* integrity error chat item */ -"bad message hash" = "ошибка хэш сообщения"; +"bad message hash" = "ошибка хэша сообщения"; /* No comment provided by engineer. */ -"Bad message hash" = "Ошибка хэш сообщения"; +"Bad message hash" = "Ошибка хэша сообщения"; /* integrity error chat item */ "bad message ID" = "ошибка ID сообщения"; @@ -788,6 +874,15 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Ошибка ID сообщения"; +/* No comment provided by engineer. */ +"Be free\nin your network" = "Будь свободен\nв своей сети"; + +/* No comment provided by engineer. */ +"Be free in your network." = "Будь свободен в своей сети."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять."; + /* No comment provided by engineer. */ "Better calls" = "Улучшенные звонки"; @@ -825,7 +920,7 @@ swipe action */ "Bio too large" = "Описание слишком длинное"; /* No comment provided by engineer. */ -"Black" = "Черная"; +"Black" = "Чёрная"; /* No comment provided by engineer. */ "Block" = "Заблокировать"; @@ -845,6 +940,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Заблокировать члена группы?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Заблокировать подписчика для всех?"; + /* marked deleted chat item preview text */ "blocked" = "заблокировано"; @@ -889,16 +987,22 @@ marked deleted chat item preview text */ "Both you and your contact can send voice messages." = "Вы и Ваш контакт можете отправлять голосовые сообщения."; /* No comment provided by engineer. */ -"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Болгарский, финский, тайский и украинский - благодаря пользователям и [Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +"Bottom bar" = "Нижнее меню"; + +/* compose placeholder for channel owner */ +"Broadcast" = "Опубликовать"; /* No comment provided by engineer. */ -"Business address" = "Бизнес адрес"; +"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Болгарский, финский, тайский и украинский - благодаря пользователям и [Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* chat link info line */ +"Business address" = "Бизнес-адрес"; /* No comment provided by engineer. */ "Business chats" = "Бизнес разговоры"; /* No comment provided by engineer. */ -"Business connection" = "Бизнес контакт"; +"Business connection" = "Бизнес-контакт"; /* No comment provided by engineer. */ "Businesses" = "Бизнесы"; @@ -906,14 +1010,11 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей – не отправлять спам."; - /* No comment provided by engineer. */ "call" = "звонок"; /* No comment provided by engineer. */ -"Call already ended!" = "Звонок уже завершен!"; +"Call already ended!" = "Звонок уже завершён!"; /* call status */ "call error" = "ошибка звонка"; @@ -934,7 +1035,10 @@ marked deleted chat item preview text */ "Camera not available" = "Камера недоступна"; /* No comment provided by engineer. */ -"Can't call contact" = "Не удается позвонить контакту"; +"can't broadcast" = "нельзя публиковать"; + +/* No comment provided by engineer. */ +"Can't call contact" = "Не удаётся позвонить контакту"; /* No comment provided by engineer. */ "Can't call member" = "Не удаётся позвонить члену группы"; @@ -987,7 +1091,7 @@ new chat action */ "Change automatic message deletion?" = "Измененить автоматическое удаление сообщений?"; /* authentication reason */ -"Change chat profiles" = "Поменять профили"; +"Change chat profiles" = "Изменить профили чата"; /* No comment provided by engineer. */ "Change database passphrase?" = "Поменять пароль базы данных?"; @@ -1032,6 +1136,58 @@ set passcode view */ /* chat item text */ "changing address…" = "смена адреса…"; +/* shown as sender role for channel messages */ +"channel" = "канал"; + +/* No comment provided by engineer. */ +"Channel" = "Канал"; + +/* No comment provided by engineer. */ +"Channel display name" = "Имя канала"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Полное имя канала (необязательно)"; + +/* alert message +alert subtitle */ +"Channel has no active relays. Please try to join later." = "У канала нет активных релеев. Попробуйте подключиться позже."; + +/* No comment provided by engineer. */ +"Channel image" = "Картинка канала"; + +/* chat link info line */ +"Channel link" = "Ссылка канала"; + +/* No comment provided by engineer. */ +"Channel preferences" = "Предпочтения канала"; + +/* No comment provided by engineer. */ +"Channel profile" = "Профиль канала"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "Профиль канала хранится на устройствах подписчиков и на чат-релеях."; + +/* snd group event chat item */ +"channel profile updated" = "профиль канала обновлён"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "Профиль канала был изменен. Если Вы сохраните его, обновлённый профиль будет отправлен подписчикам канала."; + +/* alert title */ +"Channel temporarily unavailable" = "Канал временно недоступен"; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "Канал будет удалён для всех подписчиков - это нельзя отменить!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "Канал будет удалён для Вас - это нельзя отменить!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "Канал начнёт работу с %1$d из %2$d релеев. Продолжить?"; + +/* No comment provided by engineer. */ +"Channels" = "Каналы"; + /* No comment provided by engineer. */ "Chat" = "Разговор"; @@ -1066,7 +1222,7 @@ set passcode view */ "Chat is stopped" = "Чат остановлен"; /* No comment provided by engineer. */ -"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите ее обратно до запуска чата."; +"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите её обратно до запуска чата."; /* No comment provided by engineer. */ "Chat list" = "Список чатов"; @@ -1075,7 +1231,7 @@ set passcode view */ "Chat migrated!" = "Чат мигрирован!"; /* No comment provided by engineer. */ -"Chat preferences" = "Предпочтения"; +"Chat preferences" = "Настройки чатов"; /* alert message */ "Chat preferences were changed." = "Настройки чата были изменены."; @@ -1083,36 +1239,64 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Профиль чата"; +/* No comment provided by engineer. */ +"Chat relay" = "Чат-релей"; + +/* No comment provided by engineer. */ +"Chat relays" = "Чат-релеи"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "Чат-релеи пересылают сообщения в Ваших каналах."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "Чат-релеи пересылают сообщения подписчикам каналов."; + /* No comment provided by engineer. */ "Chat theme" = "Тема чата"; /* No comment provided by engineer. */ -"Chat will be deleted for all members - this cannot be undone!" = "Разговор будет удален для всех участников - это действие нельзя отменить!"; +"Chat will be deleted for all members - this cannot be undone!" = "Разговор будет удалён для всех участников - это действие нельзя отменить!"; /* No comment provided by engineer. */ -"Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!"; +"Chat will be deleted for you - this cannot be undone!" = "Разговор будет удалён для Вас - это действие нельзя отменить!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Чат с админами"; /* No comment provided by engineer. */ "Chat with member" = "Чат с членом группы"; /* No comment provided by engineer. */ -"Chat with members before they join." = "Общайтесь с членами до того как принять их."; +"Chat with members before they join." = "Общайтесь с членами группы до того как принять их."; /* No comment provided by engineer. */ "Chats" = "Чаты"; +/* No comment provided by engineer. */ +"Chats with admins are prohibited." = "Чаты с админами запрещены."; + +/* alert message */ +"Chats with admins in public channels have no E2E encryption - use only with trusted chat relays." = "Чаты с админами в публичных каналах не имеют E2E шифрования - используйте только с доверенными чат-релеями."; + /* No comment provided by engineer. */ "Chats with members" = "Чаты с членами группы"; +/* No comment provided by engineer. */ +"Chats with members are disabled" = "Чаты с членами группы отключены"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Проверять сообщения каждые 20 минут."; /* No comment provided by engineer. */ "Check messages when allowed." = "Проверять сообщения по возможности."; +/* alert message */ +"Check relay address and try again." = "Проверьте адрес релея и попробуйте снова."; + +/* alert message */ +"Check relay name and try again." = "Проверьте имя релея и попробуйте снова."; + /* alert title */ "Check server address and try again." = "Проверьте адрес сервера и попробуйте снова."; @@ -1120,7 +1304,7 @@ set passcode view */ "Chinese and Spanish interface" = "Китайский и Испанский интерфейс"; /* No comment provided by engineer. */ -"Choose _Migrate from another device_ on the new device and scan QR code." = "Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR код."; +"Choose _Migrate from another device_ on the new device and scan QR code." = "Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR-код."; /* No comment provided by engineer. */ "Choose file" = "Выбрать файл"; @@ -1204,10 +1388,10 @@ set passcode view */ "Conditions will be automatically accepted for enabled operators on: %@." = "Условия будут автоматически приняты для включенных операторов: %@."; /* No comment provided by engineer. */ -"Configure ICE servers" = "Настройка ICE серверов"; +"Configure ICE servers" = "Настройка ICE-серверов"; /* No comment provided by engineer. */ -"Configure server operators" = "Настроить операторов серверов"; +"Configure relays" = "Настроить релеи"; /* No comment provided by engineer. */ "Confirm" = "Подтвердить"; @@ -1234,7 +1418,7 @@ set passcode view */ "Confirm password" = "Подтвердить пароль"; /* No comment provided by engineer. */ -"Confirm that you remember database passphrase to migrate it." = "Подтвердите, что Вы помните пароль базы данных для ее миграции."; +"Confirm that you remember database passphrase to migrate it." = "Подтвердите, что Вы помните пароль базы данных для её миграции."; /* No comment provided by engineer. */ "Confirm upload" = "Подтвердить загрузку"; @@ -1273,6 +1457,9 @@ server test step */ /* new chat sheet title */ "Connect via link" = "Соединиться через ссылку"; +/* No comment provided by engineer. */ +"Connect via link or QR code" = "Соединитесь по ссылке или QR"; + /* new chat sheet title */ "Connect via one-time link" = "Соединиться через одноразовую ссылку"; @@ -1280,7 +1467,7 @@ server test step */ "Connect with %@" = "Соединиться с %@"; /* No comment provided by engineer. */ -"connected" = "соединение установлено"; +"connected" = "соединен(а)"; /* No comment provided by engineer. */ "Connected" = "Соединено"; @@ -1342,12 +1529,15 @@ server test step */ /* alert title */ "Connection error" = "Ошибка соединения"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Ошибка соединения (AUTH)"; /* chat list item title (it should not be shown */ "connection established" = "соединение установлено"; +/* No comment provided by engineer. */ +"Connection failed" = "Ошибка соединения"; + /* No comment provided by engineer. */ "Connection is blocked by server operator:\n%@" = "Соединение заблокировано сервером оператора:\n%@"; @@ -1384,6 +1574,9 @@ server test step */ /* profile update event chat item */ "contact %@ changed to %@" = "контакт %1$@ изменён на %2$@"; +/* chat link info line */ +"Contact address" = "Адрес контакта"; + /* No comment provided by engineer. */ "Contact allows" = "Контакт разрешает"; @@ -1391,10 +1584,10 @@ server test step */ "Contact already exists" = "Существующий контакт"; /* No comment provided by engineer. */ -"contact deleted" = "контакт удален"; +"contact deleted" = "контакт удалён"; /* No comment provided by engineer. */ -"Contact deleted!" = "Контакт удален!"; +"Contact deleted!" = "Контакт удалён!"; /* No comment provided by engineer. */ "contact disabled" = "контакт выключен"; @@ -1412,7 +1605,7 @@ server test step */ "Contact is connected" = "Соединение с контактом установлено"; /* No comment provided by engineer. */ -"Contact is deleted." = "Контакт удален."; +"Contact is deleted." = "Контакт удалён."; /* No comment provided by engineer. */ "Contact name" = "Имена контактов"; @@ -1430,7 +1623,7 @@ server test step */ "contact should accept…" = "контакт должен принять…"; /* No comment provided by engineer. */ -"Contact will be deleted - this cannot be undone!" = "Контакт будет удален — это нельзя отменить!"; +"Contact will be deleted - this cannot be undone!" = "Контакт будет удалён - это нельзя отменить!"; /* No comment provided by engineer. */ "Contacts" = "Контакты"; @@ -1445,13 +1638,16 @@ server test step */ "Continue" = "Продолжить"; /* No comment provided by engineer. */ -"Conversation deleted!" = "Разговор удален!"; +"Contribute" = "Внести свой вклад"; /* No comment provided by engineer. */ -"Copy" = "Скопировать"; +"Conversation deleted!" = "Разговор удалён!"; /* No comment provided by engineer. */ -"Copy error" = "Ошибка копирования"; +"Copy" = "Копировать"; + +/* No comment provided by engineer. */ +"Copy error" = "Скопировать ошибку"; /* No comment provided by engineer. */ "Core version: v%@" = "Версия ядра: v%@"; @@ -1462,9 +1658,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Исправить имя на %@?"; -/* No comment provided by engineer. */ -"Create" = "Создать"; - /* No comment provided by engineer. */ "Create 1-time link" = "Создать одноразовую ссылку"; @@ -1492,6 +1685,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Создать профиль"; +/* No comment provided by engineer. */ +"Create public channel" = "Создать публичный канал"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Создать публичный канал (БЕТА)"; + /* server test step */ "Create queue" = "Создание очереди"; @@ -1501,9 +1700,15 @@ server test step */ /* No comment provided by engineer. */ "Create your address" = "Создайте Ваш адрес"; +/* No comment provided by engineer. */ +"Create your link" = "Создайте Вашу ссылку"; + /* No comment provided by engineer. */ "Create your profile" = "Создать профиль"; +/* No comment provided by engineer. */ +"Create your public address" = "Создайте Ваш публичный адрес"; + /* No comment provided by engineer. */ "Created" = "Создано"; @@ -1516,6 +1721,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Создание ссылки на архив"; +/* No comment provided by engineer. */ +"Creating channel" = "Создание канала"; + /* No comment provided by engineer. */ "Creating link…" = "Создаётся ссылка…"; @@ -1562,7 +1770,7 @@ server test step */ "Database encrypted!" = "База данных зашифрована!"; /* No comment provided by engineer. */ -"Database encryption passphrase will be updated and stored in the keychain.\n" = "Пароль базы данных будет изменен и сохранен в Keychain.\n"; +"Database encryption passphrase will be updated and stored in the keychain.\n" = "Пароль базы данных будет изменен и сохранён в Keychain.\n"; /* No comment provided by engineer. */ "Database encryption passphrase will be updated.\n" = "Пароль базы данных будет изменен.\n"; @@ -1592,10 +1800,10 @@ server test step */ "Database passphrase & export" = "Пароль и экспорт базы"; /* No comment provided by engineer. */ -"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохраненного в Keychain."; +"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохранённого в Keychain."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Введите пароль базы данных чтобы открыть чат."; +"Database passphrase is required to open chat." = "Введите пароль базы данных, чтобы открыть чат."; /* No comment provided by engineer. */ "Database upgrade" = "Обновление базы данных"; @@ -1604,7 +1812,7 @@ server test step */ "database version is newer than the app, but no down migration for: %@" = "версия базы данных новее чем приложения, но нет миграции для отката: %@"; /* No comment provided by engineer. */ -"Database will be encrypted and the passphrase stored in the keychain.\n" = "База данных будет зашифрована и пароль сохранен в Keychain.\n"; +"Database will be encrypted and the passphrase stored in the keychain.\n" = "База данных будет зашифрована и пароль сохранён в Keychain.\n"; /* No comment provided by engineer. */ "Database will be encrypted.\n" = "База данных будет зашифрована.\n"; @@ -1618,8 +1826,8 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Отладка доставки"; -/* No comment provided by engineer. */ -"Decentralized" = "Децентрализованный"; +/* relay test step */ +"Decode link" = "Расшифровать ссылку"; /* message decrypt error item */ "Decryption error" = "Ошибка расшифровки"; @@ -1662,6 +1870,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Удалить и уведомить контакт"; +/* No comment provided by engineer. */ +"Delete channel" = "Удалить канал"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Удалить канал?"; + /* No comment provided by engineer. */ "Delete chat" = "Удалить разговор"; @@ -1705,7 +1919,7 @@ swipe action */ "Delete files for all chat profiles" = "Удалить файлы во всех профилях чата"; /* chat feature */ -"Delete for everyone" = "Удалить для всех"; +"Delete for everyone" = "Удаление для всех"; /* No comment provided by engineer. */ "Delete for me" = "Удалить для меня"; @@ -1729,7 +1943,13 @@ swipe action */ "Delete list?" = "Удалить список?"; /* No comment provided by engineer. */ -"Delete member message?" = "Удалить сообщение участника?"; +"Delete member message?" = "Удалить сообщение члена группы\\?"; + +/* No comment provided by engineer. */ +"Delete member messages" = "Удалить сообщения члена группы"; + +/* alert title */ +"Delete member messages?" = "Удалить сообщения члена группы?"; /* No comment provided by engineer. */ "Delete message?" = "Удалить сообщение?"; @@ -1759,6 +1979,9 @@ alert button */ /* server test step */ "Delete queue" = "Удаление очереди"; +/* No comment provided by engineer. */ +"Delete relay" = "Удалить релей"; + /* No comment provided by engineer. */ "Delete report" = "Удалить сообщение о нарушении"; @@ -1783,6 +2006,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Удалено: %@"; +/* rcv group event chat item */ +"deleted channel" = "удалил(а) канал"; + /* rcv direct event chat item */ "deleted contact" = "удалил(а) контакт"; @@ -1868,10 +2094,16 @@ alert button */ "Direct messages" = "Прямые сообщения"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this chat." = "Личные сообщения запрещены в этой группе."; +"Direct messages between members are prohibited in this chat." = "Прямые сообщения между членами группы запрещены."; /* No comment provided by engineer. */ -"Direct messages between members are prohibited." = "Прямые сообщения между членами запрещены."; +"Direct messages between members are prohibited." = "Прямые сообщения между членами группы запрещены."; + +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "Прямые сообщения между подписчиками запрещены."; + +/* alert button */ +"Disable" = "Выключить"; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Выключить (кроме исключений)"; @@ -1889,7 +2121,7 @@ alert button */ "Disable SimpleX Lock" = "Отключить блокировку SimpleX"; /* No comment provided by engineer. */ -"disabled" = "выключено"; +"disabled" = "выключен"; /* No comment provided by engineer. */ "Disabled" = "Выключено"; @@ -1904,7 +2136,7 @@ alert button */ "Disappearing messages are prohibited in this chat." = "Исчезающие сообщения запрещены в этом чате."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited." = "Исчезающие сообщения запрещены в этой группе."; +"Disappearing messages are prohibited." = "Исчезающие сообщения запрещены."; /* No comment provided by engineer. */ "Disappears at" = "Исчезает"; @@ -1913,7 +2145,7 @@ alert button */ "Disappears at: %@" = "Исчезает: %@"; /* server test step */ -"Disconnect" = "Разрыв соединения"; +"Disconnect" = "Отключить"; /* No comment provided by engineer. */ "Disconnect desktop?" = "Отключить компьютер?"; @@ -1930,11 +2162,14 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Не отправлять историю новым членам."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Не отправлять историю новым подписчикам."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку."; /* No comment provided by engineer. */ -"Do not use credentials with proxy." = "Не использовать учетные данные с прокси."; +"Do not use credentials with proxy." = "Не использовать учётные данные с прокси."; /* No comment provided by engineer. */ "Do NOT use private routing." = "Не использовать конфиденциальную доставку."; @@ -1968,7 +2203,7 @@ chat item action */ "Download" = "Загрузить"; /* No comment provided by engineer. */ -"Download errors" = "Ошибки приема"; +"Download errors" = "Ошибки приёма"; /* No comment provided by engineer. */ "Download failed" = "Ошибка загрузки"; @@ -2009,27 +2244,39 @@ chat item action */ /* No comment provided by engineer. */ "E2E encrypted notifications." = "E2E зашифрованные нотификации."; +/* No comment provided by engineer. */ +"Easier to invite your friends 👋" = "Проще пригласить друзей 👋"; + /* chat item action */ "Edit" = "Редактировать"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Редактировать профиль канала"; + /* No comment provided by engineer. */ "Edit group profile" = "Редактировать профиль группы"; /* No comment provided by engineer. */ "Empty message!" = "Пустое сообщение!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Включить"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Включить (кроме исключений)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Включите хотя бы один чат-релей в настройках Сеть и серверы."; + /* alert title */ "Enable automatic message deletion?" = "Включить автоматическое удаление сообщений?"; /* No comment provided by engineer. */ "Enable camera access" = "Включить доступ к камере"; +/* alert title */ +"Enable chats with admins?" = "Включить чаты с админами?"; + /* No comment provided by engineer. */ "Enable disappearing messages by default." = "Включите исчезающие сообщения по умолчанию."; @@ -2045,11 +2292,11 @@ chat item action */ /* No comment provided by engineer. */ "Enable instant notifications?" = "Включить мгновенные уведомления?"; -/* No comment provided by engineer. */ -"Enable lock" = "Включить блокировку"; +/* alert title */ +"Enable link previews?" = "Включить картинки ссылок?"; /* No comment provided by engineer. */ -"Enable notifications" = "Включить уведомления"; +"Enable lock" = "Включить блокировку"; /* No comment provided by engineer. */ "Enable periodic notifications?" = "Включить периодические уведомления?"; @@ -2091,7 +2338,7 @@ chat item action */ "Encrypt local files" = "Шифровать локальные файлы"; /* No comment provided by engineer. */ -"Encrypt stored files & media" = "Шифруйте сохраненные файлы и медиа"; +"Encrypt stored files & media" = "Шифруйте сохранённые файлы и медиа"; /* No comment provided by engineer. */ "Encrypted database" = "База данных зашифрована"; @@ -2112,7 +2359,7 @@ chat item action */ "Encrypted message: keychain error" = "Зашифрованное сообщение: ошибка Keychain"; /* notification */ -"Encrypted message: no passphrase" = "Зашифрованное сообщение: пароль не сохранен"; +"Encrypted message: no passphrase" = "Зашифрованное сообщение: пароль не сохранён"; /* notification */ "Encrypted message: unexpected error" = "Зашифрованное сообщение: неожиданная ошибка"; @@ -2156,6 +2403,9 @@ chat item action */ /* call status */ "ended call %@" = "завершённый звонок %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Введите имя канала…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Введите правильный пароль."; @@ -2174,6 +2424,12 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Введите пароль выше, чтобы раскрыть!"; +/* No comment provided by engineer. */ +"Enter profile name..." = "Введите имя профиля..."; + +/* No comment provided by engineer. */ +"Enter relay name…" = "Введите имя релея…"; + /* No comment provided by engineer. */ "Enter server manually" = "Ввести сервер вручную"; @@ -2192,14 +2448,14 @@ chat item action */ /* No comment provided by engineer. */ "error" = "ошибка"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Ошибка"; /* No comment provided by engineer. */ "Error aborting address change" = "Ошибка при прекращении изменения адреса"; /* alert title */ -"Error accepting conditions" = "Ошибка приема условий"; +"Error accepting conditions" = "Ошибка приёма условий"; /* No comment provided by engineer. */ "Error accepting contact request" = "Ошибка при принятии запроса на соединение"; @@ -2210,6 +2466,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; +/* alert title */ +"Error adding relay" = "Ошибка добавления релея"; + /* alert title */ "Error adding server" = "Ошибка добавления сервера"; @@ -2246,6 +2505,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Ошибка при создании адреса"; +/* alert title */ +"Error creating channel" = "Ошибка при создании канала"; + /* No comment provided by engineer. */ "Error creating group" = "Ошибка при создании группы"; @@ -2351,6 +2613,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Ошибка сброса статистики"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Ошибка при сохранении профиля канала"; + /* alert title */ "Error saving chat list" = "Ошибка сохранения списка чатов"; @@ -2358,7 +2623,7 @@ chat item action */ "Error saving group profile" = "Ошибка при сохранении профиля группы"; /* No comment provided by engineer. */ -"Error saving ICE servers" = "Ошибка при сохранении ICE серверов"; +"Error saving ICE servers" = "Ошибка при сохранении ICE-серверов"; /* No comment provided by engineer. */ "Error saving passcode" = "Ошибка сохранения кода"; @@ -2393,6 +2658,9 @@ chat item action */ /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Ошибка настроек отчётов о доставке!"; +/* alert title */ +"Error sharing channel" = "Ошибка при публикации канала"; + /* No comment provided by engineer. */ "Error starting chat" = "Ошибка при запуске чата"; @@ -2435,6 +2703,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Ошибка: "; +/* receive error chat item */ +"error: %@" = "ошибка: %@"; + /* alert message file error text snd error text */ @@ -2489,6 +2760,9 @@ server test error */ /* No comment provided by engineer. */ "Exporting database archive…" = "Архив чата экспортируется…"; +/* No comment provided by engineer. */ +"failed" = "ошибка"; + /* No comment provided by engineer. */ "Failed to remove passphrase" = "Ошибка удаления пароля"; @@ -2499,7 +2773,7 @@ server test error */ "Faster deletion of groups." = "Ускорено удаление групп."; /* No comment provided by engineer. */ -"Faster joining and more reliable messages." = "Быстрое вступление и надежная доставка сообщений."; +"Faster joining and more reliable messages." = "Быстрое вступление и надёжная доставка сообщений."; /* No comment provided by engineer. */ "Faster sending messages." = "Ускорена отправка сообщений."; @@ -2520,7 +2794,7 @@ server test error */ "File is blocked by server operator:\n%@." = "Файл заблокирован оператором сервера:\n%@."; /* file error text */ -"File not found - most likely file was deleted or cancelled." = "Файл не найден - скорее всего, файл был удален или отменен."; +"File not found - most likely file was deleted or cancelled." = "Файл не найден - скорее всего, файл был удалён или отменен."; /* file error text */ "File server error: %@" = "Ошибка сервера файлов: %@"; @@ -2556,7 +2830,7 @@ server test error */ "Files and media are prohibited in this chat." = "Файлы и медиа запрещены в этом чате."; /* No comment provided by engineer. */ -"Files and media are prohibited." = "Файлы и медиа запрещены в этой группе."; +"Files and media are prohibited." = "Файлы и медиа запрещены."; /* No comment provided by engineer. */ "Files and media not allowed" = "Файлы и медиа не разрешены"; @@ -2564,6 +2838,9 @@ server test error */ /* No comment provided by engineer. */ "Files and media prohibited!" = "Файлы и медиа запрещены!"; +/* No comment provided by engineer. */ +"Filter" = "Фильтр"; + /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Фильтровать непрочитанные и избранные чаты."; @@ -2577,7 +2854,7 @@ server test error */ "Finally, we have them! 🚀" = "Наконец-то, мы их добавили! 🚀"; /* No comment provided by engineer. */ -"Find chats faster" = "Быстро найти чаты"; +"Find chats faster" = "Быстрый поиск чатов"; /* No comment provided by engineer. */ "Fingerprint in destination server address does not match certificate: %@." = "Хэш в адресе сервера назначения не соответствует сертификату: %@."; @@ -2590,7 +2867,7 @@ server test error */ /* relay test error server test error */ -"Fingerprint in server address does not match certificate." = "Возможно, хэш сертификата в адресе сервера неверный."; +"Fingerprint in server address does not match certificate." = "Хэш в адресе сервера не соответствует сертификату."; /* No comment provided by engineer. */ "Fix" = "Починить"; @@ -2608,11 +2885,14 @@ server test error */ "Fix not supported by contact" = "Починка не поддерживается контактом"; /* No comment provided by engineer. */ -"Fix not supported by group member" = "Починка не поддерживается членом группы."; +"Fix not supported by group member" = "Починка не поддерживается членом группы"; /* No comment provided by engineer. */ "For all moderators" = "Для всех модераторов"; +/* No comment provided by engineer. */ +"For anyone to reach you" = "Любой может связаться с Вами"; + /* servers error servers warning */ "For chat profile %@:" = "Для профиля чата %@:"; @@ -2698,9 +2978,15 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Уменьшенное потребление батареи"; +/* relay test step */ +"Get link" = "Получить ссылку"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Уведомления, когда Вас упомянули."; +/* No comment provided by engineer. */ +"Get started" = "Начать"; + /* No comment provided by engineer. */ "GIFs and stickers" = "ГИФ файлы и стикеры"; @@ -2746,7 +3032,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "группа удалена"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Ссылка группы"; /* No comment provided by engineer. */ @@ -2768,7 +3054,7 @@ servers warning */ "Group profile is stored on members' devices, not on the servers." = "Профиль группы хранится на устройствах членов, а не на серверах."; /* snd group event chat item */ -"group profile updated" = "профиль группы обновлен"; +"group profile updated" = "профиль группы обновлён"; /* alert message */ "Group profile was changed. If you save it, the updated profile will be sent to group members." = "Профиль группы изменен. Если Вы сохраните его, новый профиль будет отправлен членам группы."; @@ -2789,7 +3075,7 @@ servers warning */ "Help" = "Помощь"; /* No comment provided by engineer. */ -"Help admins moderating their groups." = "Помогайте администраторам модерировать их группы."; +"Help admins moderating their groups." = "Помогайте админам модерировать их группы."; /* No comment provided by engineer. */ "Hidden" = "Скрытое"; @@ -2801,7 +3087,7 @@ servers warning */ "Hidden profile password" = "Пароль скрытого профиля"; /* chat item action */ -"Hide" = "Спрятать"; +"Hide" = "Скрыть"; /* No comment provided by engineer. */ "Hide app screen in the recent apps." = "Скрыть экран приложения."; @@ -2818,6 +3104,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "История не отправляется новым членам."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "История не отправляется новым подписчикам."; + /* time unit */ "hours" = "часов"; @@ -2837,7 +3126,7 @@ servers warning */ "How to" = "Инфо"; /* No comment provided by engineer. */ -"How to use it" = "Про адрес"; +"How to use it" = "Как использовать"; /* No comment provided by engineer. */ "How to use your servers" = "Как использовать серверы"; @@ -2846,7 +3135,7 @@ servers warning */ "Hungarian interface" = "Венгерский интерфейс"; /* No comment provided by engineer. */ -"ICE servers (one per line)" = "ICE серверы (один на строке)"; +"ICE servers (one per line)" = "ICE-серверы (один на строке)"; /* No comment provided by engineer. */ "If you can't meet in person, show QR code in a video call, or share the link." = "Если Вы не можете встретиться лично, покажите QR-код во время видеозвонка или поделитесь ссылкой."; @@ -2857,6 +3146,9 @@ servers warning */ /* No comment provided by engineer. */ "If you enter your self-destruct passcode while opening the app:" = "Если Вы введёте код самоуничтожения при открытии приложения:"; +/* down migration warning */ +"If you joined or created channels, they will stop working permanently." = "Если Вы присоединились к каналам или создали их, они перестанут работать навсегда."; + /* No comment provided by engineer. */ "If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Если сейчас Вам нужно использовать чат, нажмите **Отложить** внизу (Вы сможете мигрировать данные чата при следующем запуске приложения)."; @@ -2870,10 +3162,10 @@ servers warning */ "Image will be received when your contact is online, please wait or check later!" = "Изображение будет принято, когда Ваш контакт будет в сети, подождите или проверьте позже!"; /* No comment provided by engineer. */ -"Immediately" = "Сразу"; +"Images" = "Изображения"; /* No comment provided by engineer. */ -"Immune to spam" = "Защищен от спама"; +"Immediately" = "Сразу"; /* No comment provided by engineer. */ "Import" = "Импортировать"; @@ -2933,7 +3225,7 @@ servers warning */ "Incognito mode" = "Режим Инкогнито"; /* No comment provided by engineer. */ -"Incognito mode protects your privacy by using a new random profile for each contact." = "Режим Инкогнито защищает Вашу конфиденциальность — для каждого контакта создается новый случайный профиль."; +"Incognito mode protects your privacy by using a new random profile for each contact." = "Режим Инкогнито защищает Вашу конфиденциальность - для каждого контакта создаётся новый случайный профиль."; /* chat list item description */ "incognito via contact address link" = "инкогнито через ссылку-контакт"; @@ -2975,7 +3267,7 @@ servers warning */ "Initial role" = "Роль при вступлении"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "SimpleX Chat для терминала"; /* No comment provided by engineer. */ "Instant" = "Мгновенно"; @@ -3010,7 +3302,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "ошибка данных чата"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Ошибка в ссылке контакта"; /* invalid chat item */ @@ -3029,7 +3321,13 @@ servers warning */ "Invalid name!" = "Неверное имя!"; /* No comment provided by engineer. */ -"Invalid QR code" = "Неверный QR код"; +"Invalid QR code" = "Ошибка QR-кода"; + +/* alert title */ +"Invalid relay address!" = "Неверный адрес релея!"; + +/* alert title */ +"Invalid relay name!" = "Неверное имя релея!"; /* No comment provided by engineer. */ "Invalid response" = "Ошибка ответа"; @@ -3053,7 +3351,13 @@ servers warning */ "Invite friends" = "Пригласить друзей"; /* No comment provided by engineer. */ -"Invite members" = "Пригласить членов группы"; +"Invite member" = "Пригласить члена группы"; + +/* No comment provided by engineer. */ +"Invite members" = "Пригласить в группу"; + +/* No comment provided by engineer. */ +"Invite someone privately" = "Пригласите конфиденциально"; /* No comment provided by engineer. */ "Invite to chat" = "Пригласить в разговор"; @@ -3077,19 +3381,19 @@ servers warning */ "iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "iOS Keychain используется для безопасного хранения пароля - это позволяет получать мгновенные уведомления."; /* No comment provided by engineer. */ -"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Пароль базы данных будет безопасно сохранен в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления."; +"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Пароль базы данных будет безопасно сохранён в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления."; /* No comment provided by engineer. */ -"IP address" = "IP адрес"; +"IP address" = "IP-адрес"; /* No comment provided by engineer. */ "Irreversible message deletion" = "Окончательное удаление сообщений"; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this chat." = "Необратимое удаление сообщений запрещено в этом чате."; +"Irreversible message deletion is prohibited in this chat." = "Необратимое удаление сообщений запрещено."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited." = "Необратимое удаление сообщений запрещено в этой группе."; +"Irreversible message deletion is prohibited." = "Необратимое удаление сообщений запрещено."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Это позволяет иметь много анонимных соединений без общих данных между ними в одном профиле пользователя."; @@ -3101,7 +3405,7 @@ servers warning */ "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Это может произойти, когда:\n1. Клиент отправителя удалил неотправленные сообщения через 2 дня, или сервер – через 30 дней.\n2. Расшифровка сообщения была невозможна, когда Вы или Ваш контакт использовали старую копию базы данных.\n3. Соединение компроментировано."; /* No comment provided by engineer. */ -"It protects your IP address and connections." = "Защищает ваш IP адрес и соединения."; +"It protects your IP address and connections." = "Защищает ваш IP-адрес и соединения."; /* No comment provided by engineer. */ "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Возможно, Вы уже соединились через эту ссылку. Если это не так, то это ошибка (%@)."; @@ -3119,7 +3423,10 @@ servers warning */ "Join" = "Вступить"; /* No comment provided by engineer. */ -"Join as %@" = "вступить как %@"; +"Join as %@" = "Вступить как %s"; + +/* No comment provided by engineer. */ +"Join channel" = "Вступить в канал"; /* new chat sheet title */ "Join group" = "Вступить в группу"; @@ -3169,6 +3476,12 @@ servers warning */ /* swipe action */ "Leave" = "Выйти"; +/* No comment provided by engineer. */ +"Leave channel" = "Покинуть канал"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Выйти из канала?"; + /* No comment provided by engineer. */ "Leave chat" = "Покинуть разговор"; @@ -3187,6 +3500,9 @@ servers warning */ /* No comment provided by engineer. */ "Less traffic on mobile networks." = "Меньше трафик в мобильных сетях."; +/* No comment provided by engineer. */ +"Let someone connect to you" = "Дайте собеседнику Вашу ссылку"; + /* email subject */ "Let's talk in SimpleX Chat" = "Давайте поговорим в SimpleX Chat"; @@ -3196,15 +3512,24 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Ограничения"; +/* No comment provided by engineer. */ +"link" = "ссылка"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Свяжите мобильное и настольное приложения! 🔗"; +/* owner verification */ +"Link signature verified." = "Подпись ссылки проверена."; + /* No comment provided by engineer. */ "Linked desktop options" = "Опции связанных компьютеров"; /* No comment provided by engineer. */ "Linked desktops" = "Связанные компьютеры"; +/* No comment provided by engineer. */ +"Links" = "Ссылки"; + /* swipe action */ "List" = "Список"; @@ -3245,10 +3570,10 @@ servers warning */ "Make profile private!" = "Сделайте профиль скрытым!"; /* No comment provided by engineer. */ -"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется."; +"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Пожалуйста, проверьте, что адреса WebRTC ICE-серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется."; /* No comment provided by engineer. */ -"Mark deleted for everyone" = "Пометить как удаленное для всех"; +"Mark deleted for everyone" = "Пометить как удалённое для всех"; /* No comment provided by engineer. */ "Mark read" = "Прочитано"; @@ -3290,14 +3615,17 @@ servers warning */ "member connected" = "соединен(а)"; /* No comment provided by engineer. */ -"member has old version" = "член имеет старую версию"; +"member has old version" = "член группы имеет старую версию"; /* item status text */ -"Member inactive" = "Член неактивен"; +"Member inactive" = "Член группы неактивен"; /* No comment provided by engineer. */ "Member is deleted - can't accept request" = "Член группы удалён - невозможно принять запрос"; +/* alert message */ +"Member messages will be deleted - this cannot be undone!" = "Сообщения члена группы будут удалены - это нельзя отменить!"; + /* chat feature */ "Member reports" = "Сообщения о нарушениях"; @@ -3311,17 +3639,20 @@ servers warning */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение."; /* alert message */ -"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!"; +"Member will be removed from chat - this cannot be undone!" = "Член будет удалён из разговора - это действие нельзя отменить!"; /* alert message */ -"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!"; +"Member will be removed from group - this cannot be undone!" = "Член группы будет удалён - это действие нельзя отменить!"; /* alert message */ -"Member will join the group, accept member?" = "Участник хочет присоединиться к группе. Принять?"; +"Member will join the group, accept member?" = "Член группы хочет присоединиться. Принять?"; /* No comment provided by engineer. */ "Members can add message reactions." = "Члены могут добавлять реакции на сообщения."; +/* No comment provided by engineer. */ +"Members can chat with admins." = "Члены группы могут общаться с админами."; + /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "Члены могут необратимо удалять отправленные сообщения. (24 часа)"; @@ -3329,7 +3660,7 @@ servers warning */ "Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам."; /* No comment provided by engineer. */ -"Members can send direct messages." = "Члены могут посылать прямые сообщения."; +"Members can send direct messages." = "Члены могут посылать личные сообщения."; /* No comment provided by engineer. */ "Members can send disappearing messages." = "Члены могут посылать исчезающие сообщения."; @@ -3338,13 +3669,13 @@ servers warning */ "Members can send files and media." = "Члены могут слать файлы и медиа."; /* No comment provided by engineer. */ -"Members can send SimpleX links." = "Члены могут отправлять ссылки SimpleX."; +"Members can send SimpleX links." = "Члены группы могут отправлять ссылки SimpleX."; /* No comment provided by engineer. */ -"Members can send voice messages." = "Члены могут отправлять голосовые сообщения."; +"Members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; /* No comment provided by engineer. */ -"Mention members 👋" = "Упоминайте участников 👋"; +"Mention members 👋" = "Упоминайте членов группы 👋"; /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -3356,7 +3687,7 @@ servers warning */ "Message delivery error" = "Ошибка доставки сообщения"; /* No comment provided by engineer. */ -"Message delivery receipts!" = "Отчеты о доставке сообщений!"; +"Message delivery receipts!" = "Отчёты о доставке сообщений!"; /* item status text */ "Message delivery warning" = "Предупреждение доставки сообщения"; @@ -3364,6 +3695,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Черновик сообщения"; +/* No comment provided by engineer. */ +"Message error" = "Ошибка сообщения"; + /* item status text */ "Message forwarded" = "Сообщение переслано"; @@ -3383,13 +3717,13 @@ servers warning */ "Message reactions are prohibited in this chat." = "Реакции на сообщения в этом чате запрещены."; /* No comment provided by engineer. */ -"Message reactions are prohibited." = "Реакции на сообщения запрещены в этой группе."; +"Message reactions are prohibited." = "Реакции на сообщения запрещены."; /* notification */ "message received" = "получено сообщение"; /* No comment provided by engineer. */ -"Message reception" = "Прием сообщений"; +"Message reception" = "Приём сообщений"; /* No comment provided by engineer. */ "Message servers" = "Серверы сообщений"; @@ -3419,11 +3753,17 @@ servers warning */ "Messages & files" = "Сообщения"; /* No comment provided by engineer. */ -"Messages are protected by **end-to-end encryption**." = "Сообщения защищены **end-to-end шифрованием**."; +"Messages are protected by **end-to-end encryption**." = "Сообщения защищены **сквозным шифрованием**."; /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Сообщения от %@ будут показаны!"; +/* No comment provided by engineer. */ +"Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages." = "Сообщения в этом канале **не защищены сквозным шифрованием**. Чат-релеи могут видеть эти сообщения."; + +/* E2EE info chat item */ +"Messages in this channel are not end-to-end encrypted. Chat relays can see these messages." = "Сообщения в этом канале не защищены сквозным шифрованием. Чат-релеи могут видеть эти сообщения."; + /* alert message */ "Messages in this chat will never be deleted." = "Сообщения в этом чате никогда не будут удалены."; @@ -3437,17 +3777,17 @@ servers warning */ "Messages were deleted after you selected them." = "Сообщения были удалены после того, как вы их выбрали."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **сквозным шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **квантово-устойчивым сквозным шифрованием** с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; + +/* No comment provided by engineer. */ +"Migrate" = "Мигрировать"; /* No comment provided by engineer. */ "Migrate device" = "Мигрировать устройство"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Миграция с другого устройства"; - /* No comment provided by engineer. */ "Migrate here" = "Мигрировать сюда"; @@ -3455,7 +3795,7 @@ servers warning */ "Migrate to another device" = "Мигрировать на другое устройство"; /* No comment provided by engineer. */ -"Migrate to another device via QR code." = "Мигрируйте на другое устройство через QR код."; +"Migrate to another device via QR code." = "Мигрируйте на другое устройство через QR-код."; /* No comment provided by engineer. */ "Migrating" = "Миграция"; @@ -3512,10 +3852,10 @@ servers warning */ "More improvements are coming soon!" = "Дополнительные улучшения скоро!"; /* No comment provided by engineer. */ -"More reliable network connection." = "Более надежное соединение с сетью."; +"More reliable network connection." = "Более надёжное соединение с сетью."; /* No comment provided by engineer. */ -"More reliable notifications" = "Более надежные уведомления"; +"More reliable notifications" = "Более надёжные уведомления"; /* item status description */ "Most likely this connection is deleted." = "Скорее всего, соединение удалено."; @@ -3538,12 +3878,18 @@ servers warning */ /* No comment provided by engineer. */ "Network & servers" = "Сеть и серверы"; +/* No comment provided by engineer. */ +"Network commitments" = "Обязательства сети"; + /* No comment provided by engineer. */ "Network connection" = "Интернет-соединение"; /* No comment provided by engineer. */ "Network decentralization" = "Децентрализация сети"; +/* conn error description */ +"Network error" = "Ошибка сети"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Ошибка сети - сообщение не было отправлено после многократных попыток."; @@ -3553,6 +3899,9 @@ servers warning */ /* No comment provided by engineer. */ "Network operator" = "Оператор сети"; +/* No comment provided by engineer. */ +"Network routers cannot know\nwho talks to whom" = "Серверы сети не могут знать,\nкто с кем общается"; + /* No comment provided by engineer. */ "Network settings" = "Настройки сети"; @@ -3562,15 +3911,24 @@ servers warning */ /* delete after time */ "never" = "никогда"; +/* No comment provided by engineer. */ +"new" = "новый"; + /* token status text */ "New" = "Новый"; +/* No comment provided by engineer. */ +"New 1-time link" = "Новая одноразовая ссылка"; + /* No comment provided by engineer. */ "New chat" = "Новый чат"; /* No comment provided by engineer. */ "New chat experience 🎉" = "Новый интерфейс 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Новый чат-релей"; + /* notification */ "New contact request" = "Новый запрос на соединение"; @@ -3599,7 +3957,7 @@ servers warning */ "New member role" = "Роль члена группы"; /* rcv group event chat item */ -"New member wants to join the group." = "Новый участник хочет присоединиться к группе."; +"New member wants to join the group." = "Новый член группы хочет присоединиться."; /* notification */ "new message" = "новое сообщение"; @@ -3617,10 +3975,10 @@ servers warning */ "New server" = "Новый сервер"; /* No comment provided by engineer. */ -"New SOCKS credentials will be used every time you start the app." = "Новые учетные данные SOCKS будут использоваться при каждом запуске приложения."; +"New SOCKS credentials will be used every time you start the app." = "Новые учётные данные SOCKS будут использоваться при каждом запуске приложения."; /* No comment provided by engineer. */ -"New SOCKS credentials will be used for each server." = "Новые учетные данные SOCKS будут использоваться для каждого сервера."; +"New SOCKS credentials will be used for each server." = "Новые учётные данные SOCKS будут использоваться для каждого сервера."; /* pref value */ "no" = "нет"; @@ -3628,9 +3986,21 @@ servers warning */ /* No comment provided by engineer. */ "No" = "Нет"; +/* No comment provided by engineer. */ +"No account. No phone. No email. No ID.\nThe most secure encryption." = "Без аккаунта. Без номера. Без email. Без ID.\nСамое безопасное шифрование."; + +/* No comment provided by engineer. */ +"No active relays" = "Нет активных релеев"; + /* Authentication unavailable */ "No app password" = "Нет кода доступа"; +/* No comment provided by engineer. */ +"No chat relays" = "Нет чат-релеев"; + +/* servers warning */ +"No chat relays enabled." = "Чат-релеи не включены."; + /* No comment provided by engineer. */ "No chats" = "Нет чатов"; @@ -3707,10 +4077,10 @@ servers warning */ "No servers for private message routing." = "Нет серверов для доставки сообщений."; /* servers error */ -"No servers to receive files." = "Нет серверов для приема файлов."; +"No servers to receive files." = "Нет серверов для приёма файлов."; /* servers error */ -"No servers to receive messages." = "Нет серверов для приема сообщений."; +"No servers to receive messages." = "Нет серверов для приёма сообщений."; /* servers error */ "No servers to send files." = "Нет серверов для отправки файлов."; @@ -3728,7 +4098,16 @@ servers warning */ "No unread chats" = "Нет непрочитанных чатов"; /* No comment provided by engineer. */ -"No user identifiers." = "Без идентификаторов пользователей."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Никто не отслеживал ваши разговоры. Никто не составлял карту ваших перемещений. Конфиденциальность не была функцией - это был образ жизни."; + +/* No comment provided by engineer. */ +"Non-profit governance" = "Некоммерческое управление"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Не более надёжный замок на чужой двери. Не более вежливый хозяин, который уважает вашу частную жизнь, но всё равно ведёт учёт всех посетителей. Вы не гость. Вы у себя дома. Ни один король не войдёт в ваш дом - вы суверенны."; + +/* alert title */ +"Not all relays connected" = "Не все релеи подключены"; /* No comment provided by engineer. */ "Not compatible!" = "Несовместимая версия!"; @@ -3773,7 +4152,7 @@ time to disappear */ "off" = "нет"; /* blur media */ -"Off" = "Выключено"; +"Off" = "Нет"; /* feature offered item */ "offered %@" = "предложил(a) %@"; @@ -3786,7 +4165,7 @@ alert button new chat action */ "Ok" = "Ок"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3795,9 +4174,15 @@ new chat action */ /* group pref value */ "on" = "да"; +/* No comment provided by engineer. */ +"On your phone, not on servers." = "На Вашем телефоне, не на серверах."; + /* No comment provided by engineer. */ "One-time invitation link" = "Одноразовая ссылка"; +/* chat link info line */ +"One-time link" = "Одноразовая ссылка"; + /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Подключаться только к **onion** хостам.\nТребуется совместимый VPN."; @@ -3807,6 +4192,9 @@ new chat action */ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion хосты не используются."; +/* No comment provided by engineer. */ +"Only channel owners can change channel preferences." = "Изменить настройки канала могут только владельцы канала."; + /* No comment provided by engineer. */ "Only chat owners can change preferences." = "Только владельцы разговора могут поменять предпочтения."; @@ -3867,12 +4255,16 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Только Ваш контакт может отправлять голосовые сообщения."; -/* alert action */ +/* alert action +alert button */ "Open" = "Открыть"; /* No comment provided by engineer. */ "Open changes" = "Открыть изменения"; +/* new chat action */ +"Open channel" = "Открыть канал"; + /* new chat action */ "Open chat" = "Открыть чат"; @@ -3885,6 +4277,9 @@ new chat action */ /* No comment provided by engineer. */ "Open conditions" = "Открыть условия"; +/* alert title */ +"Open external link?" = "Открыть внешнюю ссылку?"; + /* alert action */ "Open full link" = "Открыть полную ссылку"; @@ -3897,6 +4292,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Открытие миграции на другое устройство"; +/* new chat action */ +"Open new channel" = "Открыть новый канал"; + /* new chat action */ "Open new chat" = "Открыть новый чат"; @@ -3927,6 +4325,9 @@ new chat action */ /* alert title */ "Operator server" = "Сервер оператора"; +/* No comment provided by engineer. */ +"Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code" = "Операторы обязуются:\n- Быть независимыми\n- Минимизировать использование метаданных\n- Использовать проверенный и открытый исходный код"; + /* No comment provided by engineer. */ "Or import archive file" = "Или импортировать файл архива"; @@ -3934,17 +4335,23 @@ new chat action */ "Or paste archive link" = "Или вставьте ссылку архива"; /* No comment provided by engineer. */ -"Or scan QR code" = "Или отсканируйте QR код"; +"Or scan QR code" = "Или отсканируйте QR-код"; /* No comment provided by engineer. */ "Or securely share this file link" = "Или передайте эту ссылку"; +/* No comment provided by engineer. */ +"Or show QR in person or via video call." = "Или покажите QR лично или через видеозвонок."; + /* No comment provided by engineer. */ "Or show this code" = "Или покажите этот код"; /* No comment provided by engineer. */ "Or to share privately" = "Или поделиться конфиденциально"; +/* No comment provided by engineer. */ +"Or use this QR - print or show online." = "Или используйте этот QR - распечатайте или покажите онлайн."; + /* No comment provided by engineer. */ "Organize chats into lists" = "Организуйте чаты в списки"; @@ -3963,9 +4370,18 @@ new chat action */ /* member role */ "owner" = "владелец"; +/* No comment provided by engineer. */ +"Owner" = "Владелец"; + /* feature role */ "owners" = "владельцы"; +/* No comment provided by engineer. */ +"Owners" = "Владельцы"; + +/* No comment provided by engineer. */ +"Ownership: you can run your own relays." = "Владение: Вы можете запустить свои собственные релеи."; + /* No comment provided by engineer. */ "Passcode" = "Код доступа"; @@ -3993,6 +4409,9 @@ new chat action */ /* No comment provided by engineer. */ "Paste image" = "Вставить изображение"; +/* No comment provided by engineer. */ +"Paste link / Scan" = "Вставить ссылку / Сканировать"; + /* No comment provided by engineer. */ "Paste link to connect!" = "Вставьте ссылку, чтобы соединиться!"; @@ -4042,10 +4461,10 @@ new chat action */ "Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Пожалуйста, проверьте, что мобильный и компьютер находятся в одной и той же локальной сети, и что брандмауэр компьютера разрешает подключение.\nПожалуйста, поделитесь любыми другими ошибками с разработчиками."; /* No comment provided by engineer. */ -"Please check that you used the correct link or ask your contact to send you another one." = "Пожалуйста, проверьте, что Вы использовали правильную ссылку или попросите, чтобы Ваш контакт отправил Вам другую ссылку."; +"Please check that you used the correct link or ask your contact to send you another one." = "Пожалуйста, проверьте, что Вы использовали правильную ссылку, или попросите Ваш контакт отправить Вам новую."; /* alert message */ -"Please check your network connection with %@ and try again." = "Пожалуйста, проверьте Ваше соединение с %@ и попробуйте еще раз."; +"Please check your network connection with %@ and try again." = "Пожалуйста, проверьте Ваше соединение с %@ и попробуйте ещё раз."; /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Проверьте предпочтения Вашего контакта."; @@ -4069,16 +4488,16 @@ new chat action */ "Please remember or store it securely - there is no way to recover a lost passcode!" = "Пожалуйста, запомните или сохраните его - восстановить потерянный пароль невозможно!"; /* No comment provided by engineer. */ -"Please report it to the developers." = "Пожалуйста, сообщите об этой ошибке девелоперам."; +"Please report it to the developers." = "Пожалуйста, сообщите об этой ошибке разработчикам."; /* No comment provided by engineer. */ "Please restart the app and migrate the database to enable push notifications." = "Пожалуйста, перезапустите приложение и переместите данные чата, чтобы включить доставку уведомлений."; /* No comment provided by engineer. */ -"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Пожалуйста, надежно сохраните пароль, Вы НЕ сможете открыть чат, если потеряете его."; +"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Пожалуйста, надёжно сохраните пароль, Вы НЕ сможете открыть чат, если потеряете его."; /* No comment provided by engineer. */ -"Please store passphrase securely, you will NOT be able to change it if you lose it." = "Пожалуйста, надежно сохраните пароль, Вы НЕ сможете его поменять, если потеряете."; +"Please store passphrase securely, you will NOT be able to change it if you lose it." = "Пожалуйста, надёжно сохраните пароль, Вы НЕ сможете его поменять, если потеряете."; /* token info */ "Please try to disable and re-enable notfications." = "Попробуйте выключить и снова включить уведомления."; @@ -4101,6 +4520,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Сохранить последний черновик, вместе с вложениями."; +/* No comment provided by engineer. */ +"Preset relay address" = "Адрес релея по умолчанию"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Имя релея по умолчанию"; + /* No comment provided by engineer. */ "Preset server address" = "Адрес сервера по умолчанию"; @@ -4123,13 +4548,13 @@ new chat action */ "Privacy policy and conditions of use." = "Политика конфиденциальности и условия использования."; /* No comment provided by engineer. */ -"Privacy redefined" = "Более конфиденциальный"; +"Privacy: for owners and subscribers." = "Конфиденциальность: для владельцев и подписчиков."; /* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Частные разговоры, группы и Ваши контакты недоступны для операторов серверов."; +"Private and secure messaging." = "Конфиденциальный и безопасный обмен сообщениями."; /* No comment provided by engineer. */ -"Private filenames" = "Защищенные имена файлов"; +"Private filenames" = "Защищённые имена файлов"; /* No comment provided by engineer. */ "Private media file names." = "Конфиденциальные названия медиафайлов."; @@ -4152,6 +4577,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Таймаут конфиденциальной доставки"; +/* alert action */ +"Proceed" = "Продолжить"; + /* No comment provided by engineer. */ "Profile and server connections" = "Профиль и соединения на сервере"; @@ -4168,11 +4596,14 @@ new chat action */ "Profile theme" = "Тема профиля"; /* alert message */ -"Profile update will be sent to your contacts." = "Обновлённый профиль будет отправлен Вашим контактам."; +"Profile update will be sent to your SimpleX contacts." = "Обновление профиля будет отправлено Вашим SimpleX контактам."; /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Запретить аудио/видео звонки."; +/* No comment provided by engineer. */ +"Prohibit chats with admins." = "Запретить чаты с админами."; + /* No comment provided by engineer. */ "Prohibit irreversible message deletion." = "Запретить необратимое удаление сообщений."; @@ -4186,31 +4617,34 @@ new chat action */ "Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы."; /* No comment provided by engineer. */ -"Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы."; +"Prohibit sending direct messages to members." = "Запретить посылать личные сообщения членам группы."; /* No comment provided by engineer. */ -"Prohibit sending disappearing messages." = "Запретить посылать исчезающие сообщения."; +"Prohibit sending direct messages to subscribers." = "Запретить отправку личных сообщений подписчикам."; /* No comment provided by engineer. */ -"Prohibit sending files and media." = "Запретить слать файлы и медиа."; +"Prohibit sending disappearing messages." = "Запретить отправлять исчезающие сообщения."; + +/* No comment provided by engineer. */ +"Prohibit sending files and media." = "Запретить отправлять файлы и медиа."; /* No comment provided by engineer. */ "Prohibit sending SimpleX links." = "Запретить отправку ссылок SimpleX."; /* No comment provided by engineer. */ -"Prohibit sending voice messages." = "Запретить отправлять голосовые сообщений."; +"Prohibit sending voice messages." = "Запретить отправлять голосовые сообщения."; /* No comment provided by engineer. */ "Protect app screen" = "Защитить экран приложения"; /* No comment provided by engineer. */ -"Protect IP address" = "Защитить IP адрес"; +"Protect IP address" = "Защитить IP-адрес"; /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Защитите Ваши профили чата паролем!"; /* No comment provided by engineer. */ -"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сети и серверов*."; +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Защитите ваш IP-адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сети и серверов*."; /* No comment provided by engineer. */ "Protocol background timeout" = "Фоновый таймаут протокола"; @@ -4230,6 +4664,9 @@ new chat action */ /* No comment provided by engineer. */ "Proxy requires password" = "Прокси требует пароль"; +/* No comment provided by engineer. */ +"Public channels - speak freely 🚀" = "Публичные каналы - говорите свободно 🚀"; + /* No comment provided by engineer. */ "Push notifications" = "Доставка уведомлений"; @@ -4258,22 +4695,16 @@ new chat action */ "Read more" = "Узнать больше"; /* 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 our GitHub repository." = "Узнайте больше из нашего GitHub репозитория."; /* No comment provided by engineer. */ -"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)."; - -/* 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)."; +"Read more in User Guide." = "Узнать больше в Руководстве пользователя."; /* No comment provided by engineer. */ "Receipts are disabled" = "Отчёты о доставке выключены"; /* No comment provided by engineer. */ -"Receive errors" = "Ошибки приема"; +"Receive errors" = "Ошибки приёма"; /* No comment provided by engineer. */ "received answer…" = "получен ответ…"; @@ -4368,7 +4799,7 @@ swipe action */ "Reject contact request" = "Отклонить запрос"; /* alert title */ -"Reject member?" = "Отклонить участника?"; +"Reject member?" = "Отклонить члена группы?"; /* No comment provided by engineer. */ "rejected" = "отклонён"; @@ -4376,15 +4807,42 @@ swipe action */ /* call status */ "rejected call" = "отклонённый звонок"; -/* No comment provided by engineer. */ -"Relay server is only used if necessary. Another party can observe your IP address." = "Relay сервер используется только при необходимости. Другая сторона может видеть Ваш IP адрес."; +/* member role */ +"relay" = "релей"; /* No comment provided by engineer. */ -"Relay server protects your IP address, but it can observe the duration of the call." = "Relay сервер защищает Ваш IP адрес, но может отслеживать продолжительность звонка."; +"Relay" = "Релей"; + +/* alert title */ +"Relay address" = "Адрес релея"; + +/* alert title */ +"Relay connection failed" = "Ошибка подключения релея"; + +/* No comment provided by engineer. */ +"Relay link" = "Ссылка релея"; + +/* alert message */ +"Relay results:" = "Результаты релея:"; + +/* No comment provided by engineer. */ +"Relay server is only used if necessary. Another party can observe your IP address." = "Релей-сервер используется только при необходимости. Другая сторона может видеть Ваш IP-адрес."; + +/* No comment provided by engineer. */ +"Relay server protects your IP address, but it can observe the duration of the call." = "Релей-сервер защищает Ваш IP-адрес, но может отслеживать продолжительность звонка."; + +/* No comment provided by engineer. */ +"Relay test failed!" = "Тест релея не пройден!"; + +/* No comment provided by engineer. */ +"Reliability: many relays per channel." = "Надёжность: несколько релеев на каждый канал."; /* alert action */ "Remove" = "Удалить"; +/* alert action */ +"Remove and delete messages" = "Удалить вместе с сообщениями"; + /* No comment provided by engineer. */ "Remove archive?" = "Удалить архив?"; @@ -4403,17 +4861,29 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Удалить пароль из Keychain?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Удалить подписчика"; + +/* alert title */ +"Remove subscriber?" = "Удалить подписчика?"; + /* No comment provided by engineer. */ "removed" = "удален(а)"; +/* receive error chat item */ +"removed (%d attempts)" = "удалено (%d попыток)"; + /* rcv group event chat item */ "removed %@" = "удалил(а) %@"; +/* No comment provided by engineer. */ +"removed by operator" = "удалено оператором"; + /* profile update event chat item */ "removed contact address" = "удалён адрес контакта"; /* No comment provided by engineer. */ -"removed from group" = "удален из группы"; +"removed from group" = "удалён из группы"; /* profile update event chat item */ "removed profile picture" = "удалена картинка профиля"; @@ -4521,10 +4991,10 @@ swipe action */ "Reset to user theme" = "Сбросить на тему пользователя"; /* No comment provided by engineer. */ -"Restart the app to create a new chat profile" = "Перезапустите приложение, чтобы создать новый профиль."; +"Restart the app to create a new chat profile" = "Перезапустите приложение, чтобы создать новый профиль"; /* No comment provided by engineer. */ -"Restart the app to use imported chat database" = "Перезапустите приложение, чтобы использовать импортированные данные чата."; +"Restart the app to use imported chat database" = "Перезапустите приложение, чтобы использовать импортированные данные чата"; /* No comment provided by engineer. */ "Restore" = "Восстановить"; @@ -4554,7 +5024,7 @@ swipe action */ "Review group members" = "Одобрять членов группы"; /* admission stage */ -"Review members" = "Одобрять членов"; +"Review members" = "Одобрять членов группы"; /* admission stage description */ "Review members before admitting (\"knocking\")." = "Вручную одобрять членов для вступления в группу."; @@ -4577,6 +5047,9 @@ swipe action */ /* No comment provided by engineer. */ "Run chat" = "Запустить chat"; +/* No comment provided by engineer. */ +"Safe web links" = "Безопасные веб-ссылки"; + /* No comment provided by engineer. */ "Safely receive files" = "Получайте файлы безопасно"; @@ -4593,6 +5066,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Сохранить (и уведомить членов)"; +/* alert button */ +"Save (and notify subscribers)" = "Сохранить (и уведомить подписчиков)"; + /* alert title */ "Save admission settings?" = "Сохранить настройки вступления?"; @@ -4602,12 +5078,21 @@ chat item action */ /* No comment provided by engineer. */ "Save and notify group members" = "Сохранить и уведомить членов группы"; +/* No comment provided by engineer. */ +"Save and notify subscribers" = "Сохранить и уведомить подписчиков"; + /* No comment provided by engineer. */ "Save and reconnect" = "Сохранить и переподключиться"; /* No comment provided by engineer. */ "Save and update group profile" = "Сохранить сообщение и обновить группу"; +/* No comment provided by engineer. */ +"Save channel profile" = "Сохранить профиль канала"; + +/* alert title */ +"Save channel profile?" = "Сохранить профиль канала?"; + /* No comment provided by engineer. */ "Save group profile" = "Сохранить профиль группы"; @@ -4654,10 +5139,10 @@ chat item action */ "saved from %@" = "сохранено из %@"; /* message info title */ -"Saved message" = "Сохраненное сообщение"; +"Saved message" = "Сохранённое сообщение"; /* No comment provided by engineer. */ -"Saved WebRTC ICE servers will be removed" = "Сохраненные WebRTC ICE серверы будут удалены"; +"Saved WebRTC ICE servers will be removed" = "Сохранённые WebRTC ICE-серверы будут удалены"; /* No comment provided by engineer. */ "Saving %lld messages" = "Сохранение %lld сообщений"; @@ -4672,16 +5157,16 @@ chat item action */ "Scan code" = "Сканировать код"; /* No comment provided by engineer. */ -"Scan QR code" = "Сканировать QR код"; +"Scan QR code" = "Сканировать QR-код"; /* No comment provided by engineer. */ -"Scan QR code from desktop" = "Сканировать QR код с компьютера"; +"Scan QR code from desktop" = "Сканировать QR-код с компьютера"; /* No comment provided by engineer. */ "Scan security code from your contact's app." = "Сканируйте код безопасности из приложения контакта."; /* No comment provided by engineer. */ -"Scan server QR code" = "Сканировать QR код сервера"; +"Scan server QR code" = "Сканировать QR-код сервера"; /* No comment provided by engineer. */ "search" = "поиск"; @@ -4693,7 +5178,22 @@ chat item action */ "Search bar accepts invitation links." = "Поле поиска поддерживает ссылки-приглашения."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Искать или вставьте ссылку SimpleX"; +"Search files" = "Поиск файлов"; + +/* No comment provided by engineer. */ +"Search images" = "Поиск изображений"; + +/* No comment provided by engineer. */ +"Search links" = "Поиск ссылок"; + +/* No comment provided by engineer. */ +"Search or paste SimpleX link" = "Искать или вставить ссылку SimpleX"; + +/* No comment provided by engineer. */ +"Search videos" = "Поиск видео"; + +/* No comment provided by engineer. */ +"Search voice messages" = "Поиск голосовых сообщений"; /* network option */ "sec" = "сек"; @@ -4722,6 +5222,9 @@ chat item action */ /* chat item text */ "security code changed" = "код безопасности изменился"; +/* No comment provided by engineer. */ +"Security: owners hold channel keys." = "Безопасность: владельцы хранят ключи канала."; + /* chat item action */ "Select" = "Выбрать"; @@ -4750,7 +5253,7 @@ chat item action */ "Send" = "Отправить"; /* No comment provided by engineer. */ -"Send a live message - it will update for the recipient(s) as you type it" = "Отправить живое сообщение — оно будет обновляться для получателей по мере того, как Вы его вводите"; +"Send a live message - it will update for the recipient(s) as you type it" = "Отправить живое сообщение - оно будет обновляться для получателей по мере того, как Вы его вводите"; /* No comment provided by engineer. */ "Send contact request?" = "Отправить запрос на соединение?"; @@ -4759,7 +5262,7 @@ chat item action */ "Send delivery receipts to" = "Отправка отчётов о доставке"; /* No comment provided by engineer. */ -"Send direct message to connect" = "Отправьте сообщение чтобы соединиться"; +"Send direct message to connect" = "Отправить личное сообщение контакту"; /* No comment provided by engineer. */ "Send disappearing message" = "Отправить исчезающее сообщение"; @@ -4777,7 +5280,7 @@ chat item action */ "Send message to enable calls." = "Отправьте сообщение, чтобы включить звонки."; /* No comment provided by engineer. */ -"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Отправлять сообщения напрямую, когда IP адрес защищен, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку."; +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Отправлять сообщения напрямую, когда IP-адрес защищён, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку."; /* No comment provided by engineer. */ "Send messages directly when your or destination server does not support private routing." = "Отправлять сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку."; @@ -4789,7 +5292,7 @@ chat item action */ "Send private reports" = "Вы можете сообщить о нарушениях"; /* No comment provided by engineer. */ -"Send questions and ideas" = "Отправьте вопросы и идеи"; +"Send questions and ideas" = "Вопросы и предложения"; /* No comment provided by engineer. */ "Send receipts" = "Отчёты о доставке"; @@ -4800,12 +5303,18 @@ chat item action */ /* No comment provided by engineer. */ "Send request without message" = "Отправить запрос без сообщения"; +/* No comment provided by engineer. */ +"Send the link via any messenger - it's secure. Ask to paste into SimpleX." = "Отправьте ссылку через любой мессенджер - это безопасно. Попросите вставить её в SimpleX."; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Отправлять до 100 последних сообщений новым подписчикам."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Отправляйте Ваши конфиденциальные предложения группе."; @@ -4815,6 +5324,9 @@ chat item action */ /* No comment provided by engineer. */ "Sender may have deleted the connection request." = "Отправитель мог удалить запрос на соединение."; +/* alert message */ +"Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later." = "Отправка картинки ссылки может раскрыть Ваш IP-адрес веб-сайту. Вы можете изменить это в настройках безопасности позже."; + /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Отправка отчётов о доставке будет включена для всех контактов во всех видимых профилях чата."; @@ -4882,7 +5394,7 @@ chat item action */ "Server address is incompatible with network settings." = "Адрес сервера несовместим с настройками сети."; /* alert title */ -"Server operator changed." = "Оператор серверов изменен."; +"Server operator changed." = "Оператор сервера изменен."; /* No comment provided by engineer. */ "Server operators" = "Операторы серверов"; @@ -4893,6 +5405,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "информация сервера об очереди: %1$@\n\nпоследнее полученное сообщение: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "Для подключения к релею требуется авторизация, проверьте пароль."; + /* server test error */ "Server requires authorization to create queues, check password." = "Сервер требует авторизации для создания очередей, проверьте пароль."; @@ -4977,6 +5492,12 @@ chat item action */ /* alert message */ "Settings were changed." = "Настройки были изменены."; +/* No comment provided by engineer. */ +"Setup notifications" = "Настроить уведомления"; + +/* No comment provided by engineer. */ +"Setup routers" = "Настроить серверы"; + /* No comment provided by engineer. */ "Shape profile images" = "Форма картинок профилей"; @@ -4997,7 +5518,10 @@ chat item action */ "Share address publicly" = "Поделитесь адресом"; /* alert title */ -"Share address with contacts?" = "Поделиться адресом с контактами?"; +"Share address with SimpleX contacts?" = "Поделиться адресом с контактами SimpleX?"; + +/* No comment provided by engineer. */ +"Share channel" = "Поделиться каналом"; /* No comment provided by engineer. */ "Share from other apps." = "Поделитесь из других приложений."; @@ -5014,6 +5538,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Поделиться профилем"; +/* No comment provided by engineer. */ +"Share relay address" = "Поделиться адресом релея"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Поделитесь SimpleX адресом в социальных сетях."; @@ -5024,7 +5551,10 @@ chat item action */ "Share to SimpleX" = "Поделиться в SimpleX"; /* No comment provided by engineer. */ -"Share with contacts" = "Поделиться с контактами"; +"Share via chat" = "Поделиться в чате"; + +/* No comment provided by engineer. */ +"Share with SimpleX contacts" = "Поделиться с контактами SimpleX"; /* No comment provided by engineer. */ "Share your address" = "Поделитесь Вашим адресом"; @@ -5045,7 +5575,7 @@ chat item action */ "Show calls in phone history" = "Показать звонки в истории телефона"; /* No comment provided by engineer. */ -"Show developer options" = "Показать опции для девелоперов"; +"Show developer options" = "Показать опции для разработчиков"; /* No comment provided by engineer. */ "Show last messages" = "Показывать последние сообщения"; @@ -5060,7 +5590,7 @@ chat item action */ "Show preview" = "Показывать уведомления"; /* No comment provided by engineer. */ -"Show QR code" = "Показать QR код"; +"Show QR code" = "Показать QR-код"; /* No comment provided by engineer. */ "Show:" = "Показать:"; @@ -5081,7 +5611,7 @@ chat item action */ "SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?"; /* alert title */ -"SimpleX address settings" = "Настройки автоприема"; +"SimpleX address settings" = "Настройки автоприёма"; /* simplex link type */ "SimpleX channel link" = "SimpleX ссылка канала"; @@ -5102,7 +5632,7 @@ chat item action */ "SimpleX group link" = "SimpleX ссылка группы"; /* chat feature */ -"SimpleX links" = "SimpleX ссылки"; +"SimpleX links" = "Ссылки SimpleX"; /* No comment provided by engineer. */ "SimpleX links are prohibited." = "Ссылки SimpleX запрещены в этой группе."; @@ -5128,8 +5658,11 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Аудит SimpleX протоколов от Trail of Bits."; +/* simplex link type */ +"SimpleX relay address" = "Адрес релея SimpleX"; + /* No comment provided by engineer. */ -"Simplified incognito mode" = "Упрощенный режим Инкогнито"; +"Simplified incognito mode" = "Упрощённый режим Инкогнито"; /* No comment provided by engineer. */ "Size" = "Размер"; @@ -5144,10 +5677,10 @@ chat item action */ "Small groups (max 20)" = "Маленькие группы (до 20)"; /* No comment provided by engineer. */ -"SMP server" = "SMP сервер"; +"SMP server" = "SMP-сервер"; /* No comment provided by engineer. */ -"SOCKS proxy" = "SOCKS прокси"; +"SOCKS proxy" = "SOCKS-прокси"; /* blur media */ "Soft" = "Слабое"; @@ -5178,7 +5711,10 @@ report reason */ "Square, circle, or anything in between." = "Квадрат, круг и все, что между ними."; /* chat item text */ -"standard end-to-end encryption" = "стандартное end-to-end шифрование"; +"standard end-to-end encryption" = "стандартное сквозное шифрование"; + +/* No comment provided by engineer. */ +"Star on GitHub" = "Поставить звёздочку на GitHub"; /* No comment provided by engineer. */ "Start chat" = "Запустить чат"; @@ -5246,6 +5782,48 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Подписано"; +/* No comment provided by engineer. */ +"Subscriber" = "Подписчик"; + +/* chat feature */ +"Subscriber reports" = "Сообщения о нарушениях"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "Подписчик будет удалён из канала - это нельзя отменить!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Подписчики"; + +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Подписчики могут добавлять реакции на сообщения."; + +/* No comment provided by engineer. */ +"Subscribers can chat with admins." = "Подписчики могут общаться с админами."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Подписчики могут необратимо удалять отправленные сообщения. (24 часа)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Подписчики могут отправлять сообщения о нарушениях модераторам."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Подписчики могут отправлять личные сообщения."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Подписчики могут отправлять исчезающие сообщения."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Подписчики могут отправлять файлы и медиа."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Подписчики могут отправлять ссылки SimpleX."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Подписчики могут отправлять голосовые сообщения."; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Подписчики используют ссылку релея для подключения к каналу.\nАдрес релея был использован для настройки этого релея для канала."; + /* No comment provided by engineer. */ "Subscription errors" = "Ошибки подписки"; @@ -5273,6 +5851,9 @@ report reason */ /* No comment provided by engineer. */ "Take picture" = "Сделать фото"; +/* No comment provided by engineer. */ +"Talk to someone" = "Начните разговор"; + /* No comment provided by engineer. */ "Tap button " = "Нажмите кнопку "; @@ -5286,16 +5867,16 @@ report reason */ "Tap Connect to use bot" = "Нажмите Соединиться, чтобы использовать бот"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Нажмите Создать адрес SimpleX в меню, чтобы создать его позже."; +"Tap Join channel" = "Нажмите Войти в канал"; /* No comment provided by engineer. */ "Tap Join group" = "Нажмите Вступить в группу"; /* No comment provided by engineer. */ -"Tap to activate profile." = "Нажмите, чтобы сделать профиль активным."; +"Tap to activate profile." = "Нажмите на профиль, чтобы переключиться."; /* No comment provided by engineer. */ -"Tap to Connect" = "Нажмите чтобы соединиться"; +"Tap to Connect" = "Нажмите, чтобы соединиться"; /* No comment provided by engineer. */ "Tap to join" = "Нажмите, чтобы вступить"; @@ -5303,6 +5884,9 @@ report reason */ /* No comment provided by engineer. */ "Tap to join incognito" = "Нажмите, чтобы вступить инкогнито"; +/* No comment provided by engineer. */ +"Tap to open" = "Нажмите, чтобы открыть"; + /* No comment provided by engineer. */ "Tap to paste link" = "Нажмите, чтобы вставить ссылку"; @@ -5316,7 +5900,7 @@ report reason */ "TCP connection bg timeout" = "Фоновый таймаут TCP-соединения"; /* No comment provided by engineer. */ -"TCP connection timeout" = "Таймаут TCP соединения"; +"TCP connection timeout" = "Таймаут TCP-соединения"; /* No comment provided by engineer. */ "TCP port for messaging" = "TCP-порт для отправки сообщений"; @@ -5340,6 +5924,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Протестировать уведомления"; +/* No comment provided by engineer. */ +"Test relay" = "Тест релея"; + /* No comment provided by engineer. */ "Test server" = "Тестировать сервер"; @@ -5367,6 +5954,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Приложение улучшает конфиденциальность используя разных операторов в каждом разговоре."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "Приложение удалило это сообщение после %lld попыток его получить."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов)."; @@ -5374,7 +5964,10 @@ server test failure */ "The attempt to change database passphrase was not completed." = "Попытка поменять пароль базы данных не была завершена."; /* No comment provided by engineer. */ -"The code you scanned is not a SimpleX link QR code." = "Этот QR код не является SimpleX-ccылкой."; +"The code you scanned is not a SimpleX link QR code." = "Этот QR-код не является SimpleX-ccылкой."; + +/* conn error description */ +"The connection reached the limit of undelivered messages" = "Соединение достигло лимита недоставленных сообщений"; /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети."; @@ -5392,7 +5985,7 @@ server test failure */ "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" = "Будущее коммуникаций"; +"The first network where you own\nyour contacts and groups." = "Первая сеть, в которой Вы владеете\nсвоими контактами и группами."; /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хэш предыдущего сообщения отличается."; @@ -5407,19 +6000,22 @@ server test failure */ "The message will be deleted for all members." = "Сообщение будет удалено для всех членов группы."; /* No comment provided by engineer. */ -"The message will be marked as moderated for all members." = "Сообщение будет помечено как удаленное для всех членов группы."; +"The message will be marked as moderated for all members." = "Сообщение будет помечено как удалённое для всех членов группы."; /* No comment provided by engineer. */ "The messages will be deleted for all members." = "Сообщения будут удалены для всех членов группы."; /* No comment provided by engineer. */ -"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы."; +"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удалённые для всех членов группы."; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; /* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Древнейшая человеческая свобода - говорить с другим человеком без слежки - построенная на инфраструктуре, которая не может её предать."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Те же условия будут действовать для оператора **%s**."; /* No comment provided by engineer. */ "The second preset operator in the app!" = "Второй оператор серверов в приложении!"; @@ -5440,11 +6036,17 @@ server test failure */ "The text you pasted is not a SimpleX link." = "Вставленный текст не является SimpleX-ссылкой."; /* No comment provided by engineer. */ -"The uploaded database archive will be permanently removed from the servers." = "Загруженный архив базы данных будет навсегда удален с серверов."; +"The uploaded database archive will be permanently removed from the servers." = "Загруженный архив базы данных будет навсегда удалён с серверов."; /* No comment provided by engineer. */ "Themes" = "Темы"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Потом мы вышли в интернет, и каждая платформа попросила частичку вас - ваше имя, ваш номер, ваших друзей. Мы смирились с тем, что за возможность общаться приходится отдавать информацию о том, с кем мы общаемся. Каждое поколение людей и технологий жило так - телефон, электронная почта, мессенджеры, социальные сети. Казалось, что другого пути нет."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Другой путь есть. Сеть без номеров телефонов. Без имён пользователей. Без аккаунтов. Без каких-либо идентификаторов пользователей. Сеть, которая соединяет людей и передаёт зашифрованные сообщения, не зная, кто с кем связан."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Эти условия также будут применены к: **%@**."; @@ -5452,25 +6054,25 @@ server test failure */ "These settings are for your current profile **%@**." = "Установки для Вашего активного профиля **%@**."; /* No comment provided by engineer. */ -"They can be overridden in contact and group settings." = "Они могут быть переопределены в настройках контактов и групп."; +"They can be overridden in contact and group settings." = "Они могут быть изменены в настройках контактов и групп."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении."; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Это действие нельзя отменить - все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении."; /* No comment provided by engineer. */ -"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут."; +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Это действие нельзя отменить - все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут."; /* alert message */ "This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Это действие нельзя отменить - сообщения в этом чате, отправленные или полученные раньше чем выбрано, будут удалены."; /* No comment provided by engineer. */ -"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; +"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить - Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; /* E2EE info chat item */ -"This chat is protected by end-to-end encryption." = "Чат защищен end-to-end шифрованием."; +"This chat is protected by end-to-end encryption." = "Чат защищён сквозным шифрованием."; /* E2EE info chat item */ -"This chat is protected by quantum resistant end-to-end encryption." = "Чат защищен квантово-устойчивым end-to-end шифрованием."; +"This chat is protected by quantum resistant end-to-end encryption." = "Чат защищён квантово-устойчивым сквозным шифрованием."; /* notification title */ "this contact" = "этот контакт"; @@ -5487,6 +6089,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Эта группа больше не существует."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Это адрес чат-релея, с ним нельзя соединиться."; + +/* new chat action */ +"This is your link for channel %@!" = "Это ваша ссылка на канал %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку."; @@ -5494,7 +6102,7 @@ server test failure */ "This link was used with another mobile device, please create a new link on the desktop." = "Эта ссылка была использована на другом мобильном, пожалуйста, создайте новую ссылку на компьютере."; /* No comment provided by engineer. */ -"This message was deleted or not received yet." = "Это сообщение было удалено или еще не получено."; +"This message was deleted or not received yet." = "Это сообщение было удалено или ещё не получено."; /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Эта настройка применяется к сообщениям в Вашем текущем профиле чата **%@**."; @@ -5520,6 +6128,9 @@ server test failure */ /* No comment provided by engineer. */ "To make a new connection" = "Чтобы соединиться"; +/* No comment provided by engineer. */ +"To make SimpleX Network last." = "Чтобы сохранить сеть SimpleX для всех."; + /* No comment provided by engineer. */ "To protect against your link being replaced, you can compare contact security codes." = "Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности."; @@ -5530,10 +6141,10 @@ server test failure */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Чтобы защитить Вашу информацию, включите блокировку SimpleX Chat.\nВам будет нужно пройти аутентификацию для включения блокировки."; /* No comment provided by engineer. */ -"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений."; +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Чтобы защитить Ваш IP-адрес, приложение использует Ваши SMP-серверы для конфиденциальной доставки сообщений."; /* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, SimpleX использует разные ID для каждого Вашего контакта."; /* No comment provided by engineer. */ "To receive" = "Для получения"; @@ -5566,10 +6177,7 @@ server test failure */ "To use the servers of **%@**, accept conditions of use." = "Чтобы использовать серверы оператора **%@**, примите условия использования."; /* No comment provided by engineer. */ -"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Чтобы подтвердить end-to-end шифрование с Вашим контактом сравните (или сканируйте) код безопасности на Ваших устройствах."; - -/* No comment provided by engineer. */ -"Toggle chat list:" = "Переключите список чатов:"; +"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Чтобы подтвердить безопасность сквозного шифрования с Вашим контактом сравните (или сканируйте) код на ваших устройствах."; /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Установите режим Инкогнито при соединении."; @@ -5580,17 +6188,20 @@ server test failure */ /* No comment provided by engineer. */ "Toolbar opacity" = "Прозрачность тулбара"; +/* No comment provided by engineer. */ +"Top bar" = "Верхнее меню"; + /* No comment provided by engineer. */ "Total" = "Всего"; /* No comment provided by engineer. */ -"Transport isolation" = "Отдельные сессии для"; +"Transport isolation" = "Отдельные транспортные сессии"; /* No comment provided by engineer. */ "Transport sessions" = "Транспортные сессии"; /* subscription status explanation */ -"Trying to connect to the server used to receive messages from this connection." = "Попытка подключиться к серверу, используемому для получения сообщений от этого соединения."; +"Trying to connect to the server used to receive messages from this connection." = "Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта."; /* No comment provided by engineer. */ "Turkish interface" = "Турецкий интерфейс"; @@ -5619,6 +6230,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Разблокировать члена группы?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Разблокировать подписчика для всех?"; + /* rcv group event chat item */ "unblocked %@" = "%@ разблокирован"; @@ -5668,7 +6282,7 @@ server test failure */ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Если Вы не используете интерфейс iOS, включите режим Не отвлекать, чтобы звонок не прерывался."; /* No comment provided by engineer. */ -"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите Ваш контакт создать еще одну ссылку и проверьте Ваше соединение с сетью."; +"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите Ваш контакт создать ещё одну ссылку и проверьте Ваше соединение с сетью."; /* No comment provided by engineer. */ "Unlink" = "Забыть"; @@ -5691,12 +6305,15 @@ server test failure */ /* swipe action */ "Unread" = "Не прочитано"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Ссылка не поддерживается"; /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "До 100 последних сообщений отправляются новым членам."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "До 100 последних сообщений отправляется новым подписчикам."; + /* No comment provided by engineer. */ "Update" = "Обновить"; @@ -5709,8 +6326,11 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Обновить настройки?"; +/* rcv group event chat item */ +"updated channel profile" = "обновлён профиль канала"; + /* No comment provided by engineer. */ -"Updated conditions" = "Обновленные условия"; +"Updated conditions" = "Обновлённые условия"; /* rcv group event chat item */ "updated group profile" = "обновил(а) профиль группы"; @@ -5719,7 +6339,7 @@ server test failure */ "updated profile" = "профиль обновлён"; /* No comment provided by engineer. */ -"Updating settings will re-connect the client to all servers." = "Обновление настроек приведет к сбросу и установке нового соединения со всеми серверами."; +"Updating settings will re-connect the client to all servers." = "Обновление настроек приведёт к сбросу и установке нового соединения со всеми серверами."; /* alert button */ "Upgrade" = "Обновить"; @@ -5766,9 +6386,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Использовать %@"; -/* No comment provided by engineer. */ -"Use chat" = "Использовать чат"; - /* new chat action */ "Use current profile" = "Использовать активный профиль"; @@ -5778,6 +6395,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Использовать для сообщений"; +/* No comment provided by engineer. */ +"Use for new channels" = "Использовать для новых каналов"; + /* No comment provided by engineer. */ "Use for new connections" = "Использовать для новых соединений"; @@ -5791,17 +6411,20 @@ server test failure */ "Use iOS call interface" = "Использовать интерфейс iOS для звонков"; /* new chat action */ -"Use new incognito profile" = "Использовать новый Инкогнито профиль"; +"Use new incognito profile" = "Использовать новый профиль инкогнито"; /* No comment provided by engineer. */ "Use only local notifications?" = "Использовать только локальные нотификации?"; /* No comment provided by engineer. */ -"Use private routing with unknown servers when IP address is not protected." = "Использовать конфиденциальную доставку с неизвестными серверами, когда IP адрес не защищен."; +"Use private routing with unknown servers when IP address is not protected." = "Использовать конфиденциальную доставку с неизвестными серверами, когда IP-адрес не защищён."; /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Использовать конфиденциальную доставку с неизвестными серверами."; +/* No comment provided by engineer. */ +"Use relay" = "Использовать релей"; + /* No comment provided by engineer. */ "Use server" = "Использовать сервер"; @@ -5812,7 +6435,7 @@ server test failure */ "Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?"; /* No comment provided by engineer. */ -"Use SOCKS proxy" = "Использовать SOCKS прокси"; +"Use SOCKS proxy" = "Использовать SOCKS-прокси"; /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда порт не указан."; @@ -5826,6 +6449,9 @@ server test failure */ /* No comment provided by engineer. */ "Use the app with one hand." = "Используйте приложение одной рукой."; +/* No comment provided by engineer. */ +"Use this address in your social media profile, website, or email signature." = "Используйте этот адрес в профиле социальных сетей, на сайте или в подписи email."; + /* No comment provided by engineer. */ "Use web port" = "Использовать веб-порт"; @@ -5844,6 +6470,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Проверить"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Сверьте код с компьютером"; @@ -5865,6 +6494,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Подтвердить код безопасности"; +/* relay hostname */ +"via %@" = "через %@"; + /* No comment provided by engineer. */ "Via browser" = "В браузере"; @@ -5878,7 +6510,7 @@ server test failure */ "via one-time link" = "через одноразовую ссылку"; /* No comment provided by engineer. */ -"via relay" = "через relay сервер"; +"via relay" = "через релей-сервер"; /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "Через безопасный квантово-устойчивый протокол."; @@ -5898,6 +6530,9 @@ server test failure */ /* No comment provided by engineer. */ "Video will be received when your contact is online, please wait or check later!" = "Видео будет получено, когда Ваш контакт будет онлайн, пожалуйста, подождите или проверьте позже!"; +/* No comment provided by engineer. */ +"Videos" = "Видео"; + /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Видео и файлы до 1гб"; @@ -5931,9 +6566,18 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "Голосовые сообщения запрещены!"; +/* alert action */ +"Wait" = "Подождать"; + +/* relay test step */ +"Wait response" = "Ожидание ответа"; + /* No comment provided by engineer. */ "waiting for answer…" = "ожидается ответ…"; +/* No comment provided by engineer. */ +"Waiting for channel owner to add relays." = "Ожидает, когда владелец канала добавит релеи."; + /* No comment provided by engineer. */ "waiting for confirmation…" = "ожидается подтверждение…"; @@ -5941,10 +6585,10 @@ server test failure */ "Waiting for desktop..." = "Ожидается подключение компьютера..."; /* No comment provided by engineer. */ -"Waiting for file" = "Ожидается прием файла"; +"Waiting for file" = "Ожидается приём файла"; /* No comment provided by engineer. */ -"Waiting for image" = "Ожидается прием изображения"; +"Waiting for image" = "Ожидается приём изображения"; /* No comment provided by engineer. */ "Waiting for video" = "Ожидание видео"; @@ -5959,13 +6603,16 @@ server test failure */ "wants to connect to you!" = "хочет соединиться с Вами!"; /* No comment provided by engineer. */ -"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений"; +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: запуск чата на нескольких устройствах не поддерживается и приведёт к сбоям доставки сообщений"; /* No comment provided by engineer. */ -"Warning: you may lose some data!" = "Предупреждение: Вы можете потерять какие то данные!"; +"Warning: you may lose some data!" = "Предупреждение: Вы можете потерять некоторые данные!"; /* No comment provided by engineer. */ -"WebRTC ICE servers" = "WebRTC ICE серверы"; +"We made connecting simpler for new users." = "Мы упростили подключение для новых пользователей."; + +/* No comment provided by engineer. */ +"WebRTC ICE servers" = "WebRTC ICE-серверы"; /* time unit */ "weeks" = "недель"; @@ -5983,7 +6630,7 @@ server test failure */ "Welcome your contacts 👋" = "Приветствуйте Ваши контакты 👋"; /* No comment provided by engineer. */ -"What's new" = "Новые функции"; +"What's new" = "Что нового"; /* No comment provided by engineer. */ "When available" = "Когда возможно"; @@ -5998,7 +6645,10 @@ server test failure */ "When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем."; /* 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." = "Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом."; +"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когда Вы соединены с контактом инкогнито, тот же самый профиль инкогнито будет использоваться для групп с этим контактом."; + +/* No comment provided by engineer. */ +"Why SimpleX is built." = "Зачем создан SimpleX."; /* No comment provided by engineer. */ "WiFi" = "WiFi"; @@ -6019,10 +6669,10 @@ server test failure */ "With reduced battery usage." = "С уменьшенным потреблением батареи."; /* No comment provided by engineer. */ -"Without Tor or VPN, your IP address will be visible to file servers." = "Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов."; +"Without Tor or VPN, your IP address will be visible to file servers." = "Без Tor или VPN, Ваш IP-адрес будет доступен серверам файлов."; /* alert message */ -"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@."; +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Тора или ВПН, Ваш IP-адрес будет доступен этим серверам файлов: %@."; /* No comment provided by engineer. */ "Wrong database passphrase" = "Неправильный пароль базы данных"; @@ -6031,13 +6681,13 @@ server test failure */ "Wrong key or unknown connection - most likely this connection is deleted." = "Неверный ключ или неизвестное соединение - скорее всего, это соединение удалено."; /* file error text */ -"Wrong key or unknown file chunk address - most likely file is deleted." = "Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удален."; +"Wrong key or unknown file chunk address - most likely file is deleted." = "Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удалён."; /* No comment provided by engineer. */ "Wrong passphrase!" = "Неправильный пароль!"; /* No comment provided by engineer. */ -"XFTP server" = "XFTP сервер"; +"XFTP server" = "XFTP-сервер"; /* pref value */ "yes" = "да"; @@ -6052,7 +6702,7 @@ server test failure */ "You accepted connection" = "Вы приняли приглашение соединиться"; /* snd group event chat item */ -"you accepted this member" = "Вы приняли этого члена"; +"you accepted this member" = "Вы приняли этого члена группы"; /* No comment provided by engineer. */ "You allow" = "Вы разрешаете"; @@ -6085,13 +6735,13 @@ server test failure */ "You are already joining the group!\nRepeat join request?" = "Вы уже вступаете в группу!\nПовторить запрос на вступление?"; /* subscription status explanation */ -"You are connected to the server used to receive messages from this connection." = "Вы подключены к серверу, используемому для приема сообщений от этого соединения."; +"You are connected to the server used to receive messages from this connection." = "Вы подключены к серверу, используемому для приёма сообщений от этого соединения."; /* No comment provided by engineer. */ "You are invited to group" = "Вы приглашены в группу"; /* subscription status explanation */ -"You are not connected to the server used to receive messages from this connection (no subscription)." = "Вы не подключены к серверу, используемому для получения сообщений по этому соединению (нет подписки)."; +"You are not connected to the server used to receive messages from this connection (no subscription)." = "Вы не подключены к серверу, через который Вы получали сообщения от этого контакта (нет подписки)."; /* No comment provided by engineer. */ "You are not connected to these servers. Private routing is used to deliver messages to them." = "Вы не подключены к этим серверам. Для доставки сообщений на них используется конфиденциальная доставка."; @@ -6099,6 +6749,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "только чтение сообщений"; +/* No comment provided by engineer. */ +"you are subscriber" = "Вы подписчик"; + /* snd group event chat item */ "you blocked %@" = "Вы заблокировали %@"; @@ -6121,7 +6774,7 @@ server test failure */ "You can enable them later via app Privacy & Security settings." = "Вы можете включить их позже в настройках Конфиденциальности."; /* No comment provided by engineer. */ -"You can give another try." = "Вы можете попробовать еще раз."; +"You can give another try." = "Вы можете попробовать ещё раз."; /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Вы можете скрыть профиль или выключить уведомления - потяните его вправо."; @@ -6142,13 +6795,16 @@ server test failure */ "You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились."; +"You can share a link or a QR code - anybody will be able to join the channel." = "Вы можете поделиться ссылкой или QR-кодом - любой сможет вступить в канал."; + +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Вы можете поделиться ссылкой или QR-кодом - любой сможет присоединиться к группе. Члены группы останутся, даже если вы позже удалите ссылку."; /* 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 start chat via app Settings / Database or by restarting the app" = "Вы можете запустить чат через Настройки приложения или перезапустив приложение."; +"You can start chat via app Settings / Database or by restarting the app" = "Вы можете запустить чат через Настройки приложения или перезапустив приложение"; /* No comment provided by engineer. */ "You can still view conversation with %@ in the list of chats." = "Вы по-прежнему можете просмотреть разговор с %@ в списке чатов."; @@ -6181,16 +6837,19 @@ server test failure */ "you changed role of %@ to %@" = "Вы поменяли роль члена %1$@ на: %2$@"; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Верификация не удалась; пожалуйста, попробуйте ещё раз."; +"You commit to:\n- Only legal content in public groups\n- Respect other users - no spam" = "Вы обязуетесь:\n- Только законный контент в публичных группах\n- Уважать других пользователей - без спама"; /* No comment provided by engineer. */ -"You decide who can connect." = "Вы определяете, кто может соединиться."; +"You connected to the channel via this relay link." = "Вы подключились к каналу через эту ссылку релея."; + +/* No comment provided by engineer. */ +"You could not be verified; please try again." = "Ошибка аутентификации; попробуйте ещё раз."; /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Вы уже запросили соединение!\nПовторить запрос?"; /* No comment provided by engineer. */ -"You have to enter passphrase every time the app starts - it is not stored on the device." = "Пароль не сохранен на устройстве — Вы будете должны ввести его при каждом запуске чата."; +"You have to enter passphrase every time the app starts - it is not stored on the device." = "Пароль не сохранён на устройстве - Вы будете должны ввести его при каждом запуске чата."; /* No comment provided by engineer. */ "You invited a contact" = "Вы пригласили контакт"; @@ -6199,7 +6858,7 @@ server test failure */ "You joined this group" = "Вы вступили в эту группу"; /* No comment provided by engineer. */ -"You joined this group. Connecting to inviting group member." = "Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы."; +"You joined this group. Connecting to inviting group member." = "Вы вступили в группу. Устанавливается соединение с пригласившим Вас членом группы."; /* snd group event chat item */ "you left" = "Вы покинули группу"; @@ -6211,7 +6870,7 @@ server test failure */ "You may save the exported archive." = "Вы можете сохранить экспортированный архив."; /* No comment provided by engineer. */ -"You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Вы должны всегда использовать самую новую версию данных чата, ТОЛЬКО на одном устройстве, иначе Вы можете перестать получать сообщения от каких то контактов."; +"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." = "Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе Вы можете перестать получать сообщения от некоторых контактов."; /* No comment provided by engineer. */ "You need to allow your contact to call to be able to call them." = "Чтобы включить звонки, разрешите их Вашему контакту."; @@ -6240,6 +6899,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "Вы разблокировали %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Вы родились без аккаунта"; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Вы сможете отправлять сообщения **только после того как Ваш запрос будет принят**."; @@ -6259,7 +6921,10 @@ server test failure */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме."; /* No comment provided by engineer. */ -"You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные."; +"You will still receive calls and notifications from muted profiles when they are active." = "Вы всё равно получите звонки и уведомления в профилях без звука, когда они активные."; + +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Вы перестанете получать сообщения из этого канала. История чата сохранится."; /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Вы прекратите получать сообщения в этом разговоре. История будет сохранена."; @@ -6268,23 +6933,26 @@ server test failure */ "You will stop receiving messages from this group. Chat history will be preserved." = "Вы перестанете получать сообщения от этой группы. История чата будет сохранена."; /* No comment provided by engineer. */ -"You won't lose your contacts if you later delete your address." = "Вы сможете удалить адрес, сохранив контакты, которые через него соединились."; +"You won't lose your contacts if you later delete your address." = "Вы не потеряете контакты, если позже удалите Ваш адрес."; /* No comment provided by engineer. */ "you: " = "Вы: "; /* No comment provided by engineer. */ -"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль"; +"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Вы пытаетесь пригласить контакт, который знает Ваш профиль инкогнито, в группу, где Вы используете основной профиль"; /* 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" = "Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие Вашего основного профиля, приглашать контакты не разрешено"; +"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 business contact" = "Ваш бизнес контакт"; +"Your business contact" = "Ваш бизнес-контакт"; /* No comment provided by engineer. */ "Your calls" = "Ваши звонки"; +/* No comment provided by engineer. */ +"Your channel" = "Ваш канал"; + /* No comment provided by engineer. */ "Your chat database" = "База данных"; @@ -6316,7 +6984,10 @@ server test failure */ "Your contacts will remain connected." = "Ваши контакты сохранятся."; /* No comment provided by engineer. */ -"Your credentials may be sent unencrypted." = "Ваши учетные данные могут быть отправлены в незашифрованном виде."; +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Ваши разговоры принадлежат вам, как это всегда было до интернета. Сеть - это не место, куда вы приходите. Это место, которое вы создаёте и которым владеете. И никто не может это у вас отнять, делаете ли вы его конфиденциальным или публичным."; + +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Ваши учётные данные могут быть отправлены в незашифрованном виде."; /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Текущие данные Вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными."; @@ -6328,7 +6999,10 @@ server test failure */ "Your group" = "Ваша группа"; /* No comment provided by engineer. */ -"Your ICE servers" = "Ваши ICE серверы"; +"Your ICE servers" = "Ваши ICE-серверы"; + +/* No comment provided by engineer. */ +"Your network" = "Ваша сеть"; /* No comment provided by engineer. */ "Your preferences" = "Ваши предпочтения"; @@ -6339,6 +7013,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Ваш профиль"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "Ваш профиль **%@** будет отправлен чат-релеям и подписчикам канала.\nРелеи могут видеть сообщения канала."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Будет отправлен Ваш профиль **%@**."; @@ -6346,14 +7023,23 @@ server test failure */ "Your profile is stored on your device and only shared with your contacts." = "Ваш профиль хранится на Вашем устройстве и отправляется только контактам."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. Серверы SimpleX не могут получить доступ к Вашему профилю."; /* alert message */ -"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам."; +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профиль был изменен. Если вы сохраните его, обновлённый профиль будет отправлен всем вашим контактам."; + +/* No comment provided by engineer. */ +"Your public address" = "Ваш публичный адрес"; /* No comment provided by engineer. */ "Your random profile" = "Случайный профиль"; +/* No comment provided by engineer. */ +"Your relay address" = "Ваш адрес релея"; + +/* No comment provided by engineer. */ +"Your relay name" = "Ваше имя релея"; + /* No comment provided by engineer. */ "Your server address" = "Адрес Вашего сервера"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index ffdd2006a6..652737b4ca 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -13,15 +13,9 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 มีสี!"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[มีส่วนร่วม](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[ส่งอีเมลถึงเรา](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[ติดดาวบน GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "การโทรเสียงแบบ **encrypted จากต้นจนจบ**"; @@ -233,9 +227,6 @@ swipe action */ /* call status */ "accepted call" = "รับสายแล้ว"; -/* 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 profile" = "เพิ่มโปรไฟล์"; @@ -362,9 +353,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "รับสาย"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้"; - /* No comment provided by engineer. */ "App build: %@" = "รุ่นแอป: %@"; @@ -672,7 +660,7 @@ server test step */ /* alert title */ "Connection error" = "การเชื่อมต่อผิดพลาด"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "การเชื่อมต่อผิดพลาด (AUTH)"; /* chat list item title (it should not be shown */ @@ -720,15 +708,15 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "ดำเนินการต่อ"; +/* No comment provided by engineer. */ +"Contribute" = "มีส่วนร่วม"; + /* No comment provided by engineer. */ "Copy" = "คัดลอก"; /* No comment provided by engineer. */ "Core version: v%@" = "รุ่นหลัก: v%@"; -/* No comment provided by engineer. */ -"Create" = "สร้าง"; - /* server test step */ "Create file" = "สร้างไฟล์"; @@ -828,9 +816,6 @@ server test step */ /* time unit */ "days" = "วัน"; -/* No comment provided by engineer. */ -"Decentralized" = "กระจายอำนาจแล้ว"; - /* message decrypt error item */ "Decryption error" = "ข้อผิดพลาดในการ decrypt"; @@ -1056,7 +1041,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "แก้ไขโปรไฟล์กลุ่ม"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "เปิดใช้งาน"; /* No comment provided by engineer. */ @@ -1074,9 +1059,6 @@ alert button */ /* No comment provided by engineer. */ "Enable lock" = "เปิดใช้งานการล็อค"; -/* No comment provided by engineer. */ -"Enable notifications" = "เปิดใช้งานการแจ้งเตือน"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "เปิดใช้การแจ้งเตือนเป็นระยะๆ ไหม?"; @@ -1182,7 +1164,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "ผิดพลาด"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "ผิดพลาด"; /* No comment provided by engineer. */ @@ -1449,7 +1431,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "คำเชิญเข้าร่วมกลุ่มใช้ไม่ถูกต้องอีกต่อไป คำเชิญถูกลบโดยผู้ส่ง"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "ลิงค์กลุ่ม"; /* No comment provided by engineer. */ @@ -1551,9 +1533,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "โดยทันที"; -/* No comment provided by engineer. */ -"Immune to spam" = "มีภูมิคุ้มกันต่อสแปมและการละเมิด"; - /* No comment provided by engineer. */ "Import" = "นำเข้า"; @@ -1615,7 +1594,7 @@ server test error */ "Initial role" = "บทบาทเริ่มต้น"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "ติดตั้ง SimpleX Chat สำหรับเทอร์มินัล"; /* No comment provided by engineer. */ "Instant" = "ทันที"; @@ -1632,7 +1611,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "ข้อมูลแชทไม่ถูกต้อง"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "ลิงค์เชื่อมต่อไม่ถูกต้อง"; /* invalid chat item */ @@ -2004,9 +1983,6 @@ server test error */ /* copied message info in history */ "no text" = "ไม่มีข้อความ"; -/* No comment provided by engineer. */ -"No user identifiers." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว"; - /* No comment provided by engineer. */ "Notifications" = "การแจ้งเตือน"; @@ -2198,9 +2174,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "ความเป็นส่วนตัวและความปลอดภัย"; -/* No comment provided by engineer. */ -"Privacy redefined" = "นิยามความเป็นส่วนตัวใหม่"; - /* No comment provided by engineer. */ "Private filenames" = "ชื่อไฟล์ส่วนตัว"; @@ -2213,9 +2186,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "รหัสผ่านโปรไฟล์"; -/* alert message */ -"Profile update will be sent to your contacts." = "การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ"; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "ห้ามการโทรด้วยเสียง/วิดีโอ"; @@ -2268,13 +2238,10 @@ new chat action */ "Read more" = "อ่านเพิ่มเติม"; /* No comment provided by engineer. */ -"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)"; +"Read more in our GitHub repository." = "อ่านเพิ่มเติมในพื้นที่เก็บข้อมูล GitHub"; /* 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)"; - -/* 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)"; +"Read more in User Guide." = "อ่านเพิ่มเติมในคู่มือผู้ใช้"; /* No comment provided by engineer. */ "received answer…" = "ได้รับคำตอบ…"; @@ -2625,15 +2592,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "แชร์ที่อยู่"; -/* alert title */ -"Share address with contacts?" = "แชร์ที่อยู่กับผู้ติดต่อ?"; - /* No comment provided by engineer. */ "Share link" = "แชร์ลิงก์"; -/* No comment provided by engineer. */ -"Share with contacts" = "แชร์กับผู้ติดต่อ"; - /* No comment provided by engineer. */ "Show calls in phone history" = "แสดงการโทรในประวัติการโทร"; @@ -2694,6 +2655,9 @@ chat item action */ /* notification title */ "Somebody" = "ใครบางคน"; +/* No comment provided by engineer. */ +"Star on GitHub" = "ติดดาวบน GitHub"; + /* No comment provided by engineer. */ "Start chat" = "เริ่มแชท"; @@ -2812,9 +2776,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้!"; -/* No comment provided by engineer. */ -"The future of messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "แฮชของข้อความก่อนหน้านี้แตกต่างกัน"; @@ -2974,9 +2935,6 @@ server test failure */ /* No comment provided by engineer. */ "Use .onion hosts" = "ใช้โฮสต์ .onion"; -/* No comment provided by engineer. */ -"Use chat" = "ใช้แชท"; - /* No comment provided by engineer. */ "Use for new connections" = "ใช้สำหรับการเชื่อมต่อใหม่"; @@ -3175,9 +3133,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง."; -/* No comment provided by engineer. */ -"You decide who can connect." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น"; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "คุณต้องใส่รหัสผ่านทุกครั้งที่เริ่มแอป - รหัสผ่านไม่ได้จัดเก็บไว้ในอุปกรณ์"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 30d83d16dd..fb3bf39168 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(bu cihaz v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Katkıda bulun](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Bize e-posta gönder](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Bize GitHub'da yıldız verin](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan."; @@ -398,9 +392,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Aktif bağlantılar"; -/* 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." = "Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek."; - /* No comment provided by engineer. */ "Add friends" = "Arkadaş ekle"; @@ -647,9 +638,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Aramayı cevapla"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir."; - /* No comment provided by engineer. */ "App build: %@" = "Uygulama sürümü: %@"; @@ -891,7 +879,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarca, Fince, Tayca ve Ukraynaca - kullanıcılara ve [Weblate] e teşekkürler! (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "İş adresi"; /* No comment provided by engineer. */ @@ -906,9 +894,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz:\n- herkese açık gruplarda yalnızca yasal içerik göndermek.\n- diğer kullanıcılara saygı göstermek – spam yapmamak."; - /* No comment provided by engineer. */ "call" = "Ara"; @@ -1092,7 +1077,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Sohbet senden silinecek - bu geri alınamaz!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Yöneticilerle sohbet et"; /* No comment provided by engineer. */ @@ -1206,9 +1192,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE sunucularını ayarla"; -/* No comment provided by engineer. */ -"Configure server operators" = "Sunucu operatörlerini yapılandır"; - /* No comment provided by engineer. */ "Confirm" = "Onayla"; @@ -1342,7 +1325,7 @@ server test step */ /* alert title */ "Connection error" = "Bağlantı hatası"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Bağlantı hatası (DOĞRULAMA)"; /* chat list item title (it should not be shown */ @@ -1444,6 +1427,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Devam et"; +/* No comment provided by engineer. */ +"Contribute" = "Katkıda bulun"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Sohbet silindi!"; @@ -1462,9 +1448,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "İsim %@ olarak düzeltilsin mi?"; -/* No comment provided by engineer. */ -"Create" = "Oluştur"; - /* No comment provided by engineer. */ "Create 1-time link" = "Tek kullanımlık bağlantı oluştur"; @@ -1618,9 +1601,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Hata ayıklama teslimatı"; -/* No comment provided by engineer. */ -"Decentralized" = "Merkezi Olmayan"; - /* message decrypt error item */ "Decryption error" = "Şifre çözme hatası"; @@ -2018,7 +1998,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Boş mesaj!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Etkinleştir"; /* No comment provided by engineer. */ @@ -2048,9 +2028,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "Kilidi etkinleştir"; -/* No comment provided by engineer. */ -"Enable notifications" = "Bildirimleri etkinleştir"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Periyodik bildirimler etkinleştirilsin mi?"; @@ -2192,7 +2169,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "hata"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Hata"; /* No comment provided by engineer. */ @@ -2730,7 +2707,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "grup silindi"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Grup bağlantısı"; /* No comment provided by engineer. */ @@ -2856,9 +2833,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Hemen"; -/* No comment provided by engineer. */ -"Immune to spam" = "Spam ve kötüye kullanıma karşı bağışıklı"; - /* No comment provided by engineer. */ "Import" = "İçe aktar"; @@ -2959,7 +2933,7 @@ servers warning */ "Initial role" = "Başlangıç rolü"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Terminal için SimpleX Chat'i indir"; /* No comment provided by engineer. */ "Instant" = "Anında"; @@ -2994,7 +2968,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "geçersi̇z sohbet verisi"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Geçersiz bağlanma bağlantısı"; /* invalid chat item */ @@ -3429,9 +3403,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Cihazı taşıma"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Başka bir cihazdan geçiş yapın"; - /* No comment provided by engineer. */ "Migrate here" = "Buraya göç edin"; @@ -3708,9 +3679,6 @@ servers warning */ /* No comment provided by engineer. */ "No unread chats" = "Okunmamış sohbet yok"; -/* No comment provided by engineer. */ -"No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok."; - /* No comment provided by engineer. */ "Not compatible!" = "Uyumlu değil!"; @@ -3767,7 +3735,7 @@ alert button new chat action */ "Ok" = "Tamam"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "TAMAM"; /* No comment provided by engineer. */ @@ -3848,7 +3816,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sadece karşıdaki kişi sesli mesajlar gönderebilir."; -/* alert action */ +/* alert action +alert button */ "Open" = "Aç"; /* No comment provided by engineer. */ @@ -4103,12 +4072,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Gizlilik politikası ve kullanım koşulları."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Gizlilik yeniden tanımlandı"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez."; - /* No comment provided by engineer. */ "Private filenames" = "Gizli dosya adları"; @@ -4148,9 +4111,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Profil teması"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profil güncellemesi kişilerinize gönderilecektir."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Sesli/görüntülü aramaları yasakla."; @@ -4239,16 +4199,10 @@ new chat action */ "Read more" = "Dahasını oku"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "GitHub deposunda daha fazlasını okuyun."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(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)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "[GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Kullanıcı Rehberinde daha fazlasını okuyun."; /* No comment provided by engineer. */ "Receipts are disabled" = "Alındı onayları devre dışı bırakıldı"; @@ -4977,9 +4931,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Adresinizi herkese açık olarak paylaşın"; -/* alert title */ -"Share address with contacts?" = "Kişilerle adres paylaşılsın mı?"; - /* No comment provided by engineer. */ "Share from other apps." = "Diğer uygulamalardan paylaşın."; @@ -5004,9 +4955,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "SimpleX ile paylaş"; -/* No comment provided by engineer. */ -"Share with contacts" = "Kişilerle paylaş"; - /* No comment provided by engineer. */ "Share your address" = "Adresini paylaş"; @@ -5161,6 +5109,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "standart uçtan uca şifreleme"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Bize GitHub'da yıldız verin"; + /* No comment provided by engineer. */ "Start chat" = "Sohbeti başlat"; @@ -5266,9 +5217,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "Botu kullanmak için Bağlan tuşuna bas"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun."; - /* No comment provided by engineer. */ "Tap Join group" = "Gruba katıl'a dokunun"; @@ -5372,9 +5320,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Gizli mesajlaşmanın yeni nesli"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Önceki mesajın hash'i farklı."; @@ -5549,9 +5494,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın)."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Sohbet listesini değiştir:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Bağlanırken gizli moda geçiş yap."; @@ -5669,7 +5611,7 @@ server test failure */ /* swipe action */ "Unread" = "Okunmamış"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Desteklenmeyen bağlantı bağlantısı"; /* No comment provided by engineer. */ @@ -5744,9 +5686,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "%@ kullan"; -/* No comment provided by engineer. */ -"Use chat" = "Sohbeti kullan"; - /* new chat action */ "Use current profile" = "Şu anki profili kullan"; @@ -6155,9 +6094,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Doğrulanamadınız; lütfen tekrar deneyin."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Zaten bağlantı isteğinde bulundunuz!\nBağlantı isteği tekrarlansın mı?"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index bf86d6a339..a0d9490b00 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(цей пристрій v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внесок](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Напишіть нам електронною поштою](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Зірка на GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Додати контакт**: створити нове посилання-запрошення."; @@ -398,9 +392,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Активні з'єднання"; -/* 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 friends" = "Додайте друзів"; @@ -641,9 +632,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Відповісти на дзвінок"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Кожен може хостити сервери."; - /* No comment provided by engineer. */ "App build: %@" = "Збірка програми: %@"; @@ -879,7 +867,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Болгарською, фінською, тайською та українською мовами - завдяки користувачам та [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Адреса підприємства"; /* No comment provided by engineer. */ @@ -894,9 +882,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Використовуючи SimpleX Chat, ви погоджуєтеся:\n- надсилати лише легальний контент у публічних групах.\n- поважати інших користувачів - без спаму."; - /* No comment provided by engineer. */ "call" = "дзвонити"; @@ -1080,7 +1065,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Чат буде видалено для вас - цю дію неможливо скасувати!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Чат з адміністраторами"; /* No comment provided by engineer. */ @@ -1194,9 +1180,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Налаштування серверів ICE"; -/* No comment provided by engineer. */ -"Configure server operators" = "Налаштувати операторів сервера"; - /* No comment provided by engineer. */ "Confirm" = "Підтвердити"; @@ -1330,7 +1313,7 @@ server test step */ /* alert title */ "Connection error" = "Помилка підключення"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Помилка підключення (AUTH)"; /* chat list item title (it should not be shown */ @@ -1429,6 +1412,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Продовжуйте"; +/* No comment provided by engineer. */ +"Contribute" = "Внесок"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Розмова видалена!"; @@ -1447,9 +1433,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Виправити ім'я на %@?"; -/* No comment provided by engineer. */ -"Create" = "Створити"; - /* No comment provided by engineer. */ "Create 1-time link" = "Створити одноразове посилання"; @@ -1603,9 +1586,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Доставка налагодження"; -/* No comment provided by engineer. */ -"Decentralized" = "Децентралізований"; - /* message decrypt error item */ "Decryption error" = "Помилка розшифровки"; @@ -2000,7 +1980,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Порожнє повідомлення!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Увімкнути"; /* No comment provided by engineer. */ @@ -2030,9 +2010,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "Увімкнути блокування"; -/* No comment provided by engineer. */ -"Enable notifications" = "Увімкнути сповіщення"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "Увімкнути періодичні сповіщення?"; @@ -2174,7 +2151,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "помилка"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Помилка"; /* No comment provided by engineer. */ @@ -2706,7 +2683,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "групу видалено"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Посилання на групу"; /* No comment provided by engineer. */ @@ -2832,9 +2809,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Негайно"; -/* No comment provided by engineer. */ -"Immune to spam" = "Імунітет до спаму та зловживань"; - /* No comment provided by engineer. */ "Import" = "Імпорт"; @@ -2935,7 +2909,7 @@ servers warning */ "Initial role" = "Початкова роль"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Встановіть SimpleX Chat для терміналу"; /* No comment provided by engineer. */ "Instant" = "Миттєво"; @@ -2970,7 +2944,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "невірні дані чату"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Неправильне посилання для підключення"; /* invalid chat item */ @@ -3399,9 +3373,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Перенести пристрій"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Перехід з іншого пристрою"; - /* No comment provided by engineer. */ "Migrate here" = "Мігруйте сюди"; @@ -3678,9 +3649,6 @@ servers warning */ /* No comment provided by engineer. */ "No unread chats" = "Немає непрочитаних чатів"; -/* No comment provided by engineer. */ -"No user identifiers." = "Ніяких ідентифікаторів користувачів."; - /* No comment provided by engineer. */ "Not compatible!" = "Не сумісні!"; @@ -3737,7 +3705,7 @@ alert button new chat action */ "Ok" = "Гаразд"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "ОК"; /* No comment provided by engineer. */ @@ -3812,7 +3780,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Тільки ваш контакт може надсилати голосові повідомлення."; -/* alert action */ +/* alert action +alert button */ "Open" = "Відкрито"; /* No comment provided by engineer. */ @@ -4058,12 +4027,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Політика конфіденційності та умови використання."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Конфіденційність переглянута"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Приватні чати, групи та ваші контакти недоступні для операторів сервера."; - /* No comment provided by engineer. */ "Private filenames" = "Приватні імена файлів"; @@ -4103,9 +4066,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Тема профілю"; -/* alert message */ -"Profile update will be sent to your contacts." = "Оновлення профілю буде надіслано вашим контактам."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Заборонити аудіо/відеодзвінки."; @@ -4194,16 +4154,10 @@ new chat action */ "Read more" = "Читати далі"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Читайте більше в нашому GitHub репозиторії."; /* No comment provided by engineer. */ -"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)."; - -/* 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)."; +"Read more in User Guide." = "Читайте більше в User Guide."; /* No comment provided by engineer. */ "Receipts are disabled" = "Підтвердження виключені"; @@ -4923,9 +4877,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Поділіться адресою публічно"; -/* alert title */ -"Share address with contacts?" = "Поділіться адресою з контактами?"; - /* No comment provided by engineer. */ "Share from other apps." = "Діліться з інших програм."; @@ -4950,9 +4901,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Поділіться з SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Поділіться з контактами"; - /* No comment provided by engineer. */ "Share your address" = "Поділіться своєю адресою"; @@ -5107,6 +5055,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "стандартне наскрізне шифрування"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Зірка на GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Почати чат"; @@ -5209,9 +5160,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to send request" = "Натисніть Підключитися, щоб відправити запит"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше."; - /* No comment provided by engineer. */ "Tap Join group" = "Натисніть Приєднатися до групи"; @@ -5315,9 +5263,6 @@ server test failure */ /* 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." = "Хеш попереднього повідомлення відрізняється."; @@ -5486,9 +5431,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях."; -/* No comment provided by engineer. */ -"Toggle chat list:" = "Перемикання списку чату:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Увімкніть інкогніто при підключенні."; @@ -5606,7 +5548,7 @@ server test failure */ /* swipe action */ "Unread" = "Непрочитане"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Несумісне посилання для підключення"; /* No comment provided by engineer. */ @@ -5681,9 +5623,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "Використовуйте %@"; -/* No comment provided by engineer. */ -"Use chat" = "Використовуйте чат"; - /* new chat action */ "Use current profile" = "Використовувати поточний профіль"; @@ -6092,9 +6031,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Вас не вдалося верифікувати, спробуйте ще раз."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ви вирішуєте, хто може під'єднатися."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ви вже надіслали запит на підключення!\nПовторити запит на підключення?"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 43a0f665e4..3893351fdd 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(此设备 v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[贡献](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[给我们发电邮](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接."; @@ -395,9 +389,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "活动连接"; -/* 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 friends" = "添加好友"; @@ -647,9 +638,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "接听来电"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "任何人都可以托管服务器。"; - /* No comment provided by engineer. */ "App build: %@" = "应用程序构建:%@"; @@ -791,6 +779,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "错误消息 ID"; +/* No comment provided by engineer. */ +"Be free in your network." = "在你的网络中自由畅行。"; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。"; + /* No comment provided by engineer. */ "Better calls" = "更佳的通话"; @@ -894,7 +888,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "保加利亚语、芬兰语、泰语和乌克兰语——感谢用户和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "企业地址"; /* No comment provided by engineer. */ @@ -909,9 +903,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。"; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。"; - /* No comment provided by engineer. */ "call" = "呼叫"; @@ -1095,7 +1086,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "将为你删除聊天 - 此操作无法撤销!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "和管理员聊天"; /* No comment provided by engineer. */ @@ -1209,9 +1201,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "配置 ICE 服务器"; -/* No comment provided by engineer. */ -"Configure server operators" = "配置服务器运营方"; - /* No comment provided by engineer. */ "Confirm" = "确认"; @@ -1345,7 +1334,7 @@ server test step */ /* alert title */ "Connection error" = "连接错误"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "连接错误(AUTH)"; /* chat list item title (it should not be shown */ @@ -1447,6 +1436,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "继续"; +/* No comment provided by engineer. */ +"Contribute" = "贡献"; + /* No comment provided by engineer. */ "Conversation deleted!" = "对话已删除!"; @@ -1465,9 +1457,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "将名称更正为 %@?"; -/* No comment provided by engineer. */ -"Create" = "创建"; - /* No comment provided by engineer. */ "Create 1-time link" = "创建一次性链接"; @@ -1621,9 +1610,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "调试交付"; -/* No comment provided by engineer. */ -"Decentralized" = "分散式"; - /* message decrypt error item */ "Decryption error" = "解密错误"; @@ -2024,7 +2010,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "空消息!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "启用"; /* No comment provided by engineer. */ @@ -2054,9 +2040,6 @@ chat item action */ /* No comment provided by engineer. */ "Enable lock" = "启用锁定"; -/* No comment provided by engineer. */ -"Enable notifications" = "启用通知"; - /* No comment provided by engineer. */ "Enable periodic notifications?" = "启用定期通知?"; @@ -2198,7 +2181,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "错误"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "错误"; /* No comment provided by engineer. */ @@ -2752,7 +2735,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "群被删除了"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "群组链接"; /* No comment provided by engineer. */ @@ -2881,9 +2864,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "立即"; -/* No comment provided by engineer. */ -"Immune to spam" = "不受垃圾和骚扰消息影响"; - /* No comment provided by engineer. */ "Import" = "导入"; @@ -2984,7 +2964,7 @@ servers warning */ "Initial role" = "初始角色"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "安装用于终端的 SimpleX Chat"; /* No comment provided by engineer. */ "Instant" = "即时"; @@ -3019,7 +2999,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "无效聊天数据"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "无效的连接链接"; /* invalid chat item */ @@ -3460,9 +3440,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "迁移设备"; -/* No comment provided by engineer. */ -"Migrate from another device" = "从另一台设备迁移"; - /* No comment provided by engineer. */ "Migrate here" = "迁移到此处"; @@ -3743,7 +3720,10 @@ servers warning */ "No unread chats" = "没有未读聊天"; /* No comment provided by engineer. */ -"No user identifiers." = "没有用户标识符。"; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。"; /* No comment provided by engineer. */ "Not compatible!" = "不兼容!"; @@ -3801,7 +3781,7 @@ alert button new chat action */ "Ok" = "好的"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "好的"; /* No comment provided by engineer. */ @@ -3882,7 +3862,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "只有您的联系人可以发送语音消息。"; -/* alert action */ +/* alert action +alert button */ "Open" = "打开"; /* No comment provided by engineer. */ @@ -4134,12 +4115,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "隐私政策和使用条款。"; -/* No comment provided by engineer. */ -"Privacy redefined" = "重新定义隐私"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "服务器运营方无法访问私密聊天、群组和你的联系人。"; - /* No comment provided by engineer. */ "Private filenames" = "私密文件名"; @@ -4179,9 +4154,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "个人资料主题"; -/* alert message */ -"Profile update will be sent to your contacts." = "个人资料更新将被发送给您的联系人。"; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "禁止音频/视频通话。"; @@ -4270,16 +4242,10 @@ new chat action */ "Read more" = "阅读更多"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。"; +"Read more in our GitHub repository." = "在我们的 GitHub 仓库 中阅读更多信息。"; /* No comment provided by engineer. */ -"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) 中阅读更多内容。"; - -/* 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) 中阅读更多信息。"; +"Read more in User Guide." = "阅读更多User Guide。"; /* No comment provided by engineer. */ "Receipts are disabled" = "回执已禁用"; @@ -5020,9 +4986,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "公开分享地址"; -/* alert title */ -"Share address with contacts?" = "与联系人分享地址?"; - /* No comment provided by engineer. */ "Share from other apps." = "从其他应用程序共享。"; @@ -5047,9 +5010,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "分享到 SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "与联系人分享"; - /* No comment provided by engineer. */ "Share your address" = "分享地址"; @@ -5204,6 +5164,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "标准端到端加密"; +/* No comment provided by engineer. */ +"Star on GitHub" = "在 GitHub 上加星"; + /* No comment provided by engineer. */ "Start chat" = "开始聊天"; @@ -5306,9 +5269,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "轻按“连接”使用机器人"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址”"; - /* No comment provided by engineer. */ "Tap Join group" = "轻按加入群"; @@ -5412,9 +5372,6 @@ server test failure */ /* 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." = "上一条消息的散列不同。"; @@ -5439,6 +5396,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。"; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "应用中的第二个预设运营方!"; @@ -5460,6 +5420,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "主题"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。"; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。"; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "这些条件将同样适用于: **%@**。"; @@ -5583,9 +5549,6 @@ server test failure */ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。"; -/* No comment provided by engineer. */ -"Toggle chat list:" = "切换聊天列表:"; - /* No comment provided by engineer. */ "Toggle incognito when connecting." = "在连接时切换隐身模式。"; @@ -5703,7 +5666,7 @@ server test failure */ /* swipe action */ "Unread" = "未读"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "不支持的连接链接"; /* No comment provided by engineer. */ @@ -5778,9 +5741,6 @@ server test failure */ /* No comment provided by engineer. */ "Use %@" = "使用 %@"; -/* No comment provided by engineer. */ -"Use chat" = "使用聊天"; - /* new chat action */ "Use current profile" = "使用当前配置文件"; @@ -6198,9 +6158,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "您的身份无法验证,请再试一次。"; -/* No comment provided by engineer. */ -"You decide who can connect." = "你决定谁可以连接。"; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "您已经请求连接了!\n重复连接请求?"; @@ -6252,6 +6209,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "您解封了 %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "你生来就没有账户。"; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "**只有在你的请求被接受后**你才能发送消息。"; @@ -6321,6 +6281,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "与您的联系人保持连接。"; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。"; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "你的凭据可能以未经加密的方式被发送。"; diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 65f0acd86c..98845365fc 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -16,13 +16,19 @@ val simplexAssetsLocal = file("src/commonMain/resources/assets/simplex") val hasSimplexAssets = simplexAssetsDir != null if (simplexAssetsDir != null) { - val resolvedAssetsDir = rootProject.rootDir.resolve(simplexAssetsDir).absolutePath - tasks.register("copySimplexAssets") { - commandLine( - "${rootProject.rootDir}/../../scripts/android/copy-assets.sh", - resolvedAssetsDir, - simplexAssetsLocal.absolutePath - ) + val resolvedAssetsDir = rootProject.rootDir.resolve(simplexAssetsDir) + val srcImagesDir = resolvedAssetsDir.resolve("multiplatform/resources/MR/images") + val verifySimplexAssets = tasks.register("verifySimplexAssets") { + doLast { + if (!srcImagesDir.isDirectory) { + throw GradleException("Source assets not found: $srcImagesDir (run resize.sh first)") + } + } + } + tasks.register("copySimplexAssets") { + dependsOn(verifySimplexAssets) + from(srcImagesDir) + into(simplexAssetsLocal.resolve("MR/images")) } } else { tasks.register("cleanSimplexAssets") { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 56279a5143..011619bab0 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -394,7 +394,7 @@ private fun ActiveCallOverlayLayout( DisabledBackgroundCallsButton() } - BoxWithConstraints(Modifier.padding(start = 6.dp, end = 6.dp, bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + BoxWithConstraints(Modifier.navigationBarsPadding().padding(start = 6.dp, end = 6.dp, bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { val size = ((maxWidth - DEFAULT_PADDING_HALF * 4) / 5).coerceIn(0.dp, 60.dp) // limiting max width for tablets/wide screens, will be displayed in the center val padding = ((min(420.dp, maxWidth) - size * 5) / 4).coerceAtLeast(0.dp) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt index d9d3af7bb7..a4fc74f6d4 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt @@ -10,7 +10,7 @@ import chat.simplex.res.MR @Composable actual fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference, onclick: (() -> Unit)?) { if (user == null) { - OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onclick = onclick) + OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.get_started, onboarding = OnboardingStage.Step2_CreateProfile, onclick = onclick) } else { OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onclick = onclick) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index e1696fe37b..7542a0b8c6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -142,10 +142,8 @@ fun MainScreen() { when { onboarding == OnboardingStage.Step1_SimpleXInfo && chatModel.migrationState.value != null -> { // In migration process. Nothing should interrupt it, that's why it's the first branch in when() - SimpleXInfo(chatModel, onboarding = true) - if (appPlatform.isDesktop) { - ModalManager.fullscreen.showInView() - } + if (appPlatform.isDesktop) DesktopOnboarding(onboarding, chatModel) + else SimpleXInfo(chatModel, onboarding = true) } chatModel.dbMigrationInProgress.value -> DefaultProgressView(stringResource(MR.strings.database_migration_in_progress)) chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database)) @@ -175,36 +173,31 @@ fun MainScreen() { } } } - else -> AnimatedContent(targetState = onboarding, - transitionSpec = { - if (targetState > initialState) { - fromEndToStartTransition() - } else { - fromStartToEndTransition() - }.using(SizeTransform(clip = false)) - } - ) { state -> - when (state) { - OnboardingStage.OnboardingComplete -> { /* handled out of AnimatedContent block */} - OnboardingStage.Step1_SimpleXInfo -> { - SimpleXInfo(chatModel, onboarding = true) - if (appPlatform.isDesktop) { - ModalManager.fullscreen.showInView() + else -> { + if (appPlatform.isDesktop) { + DesktopOnboarding(onboarding, chatModel) + } else { + AnimatedContent(targetState = onboarding, + transitionSpec = { + if (targetState > initialState) { + fromEndToStartTransition() + } else { + fromStartToEndTransition() + }.using(SizeTransform(clip = false)) + } + ) { state -> + when (state) { + OnboardingStage.OnboardingComplete -> {} + OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) + OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} + OnboardingStage.LinkAMobile -> LinkAMobile() + OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) + OnboardingStage.Step3_ChooseServerOperators, + OnboardingStage.Step3_CreateSimpleXAddress, + OnboardingStage.Step4_SetNotificationsMode -> YourNetworkView(chatModel) + OnboardingStage.Step4_NetworkCommitments -> OnboardingConditionsView(chatModel) } } - OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} - OnboardingStage.LinkAMobile -> LinkAMobile() - OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) - OnboardingStage.Step3_ChooseServerOperators -> { - val modalData = remember { ModalData() } - modalData.OnboardingConditionsView() - if (appPlatform.isDesktop) { - ModalManager.fullscreen.showInView() - } - } - // Ensure backwards compatibility with old onboarding stage for address creation, otherwise notification setup would be skipped - OnboardingStage.Step3_CreateSimpleXAddress -> SetNotificationsMode(chatModel) - OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } } } @@ -276,6 +269,27 @@ fun MainScreen() { } } +@Composable +private fun DesktopOnboarding(onboarding: OnboardingStage, chatModel: ChatModel) { + if (onboarding == OnboardingStage.LinkAMobile) { + LinkAMobile() + ModalManager.fullscreen.showInView() + } else { + DesktopOnboardingShell(onboarding) { + when (onboarding) { + OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) + OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} + OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) + OnboardingStage.Step3_ChooseServerOperators, + OnboardingStage.Step3_CreateSimpleXAddress, + OnboardingStage.Step4_SetNotificationsMode -> YourNetworkView(chatModel) + OnboardingStage.Step4_NetworkCommitments -> OnboardingConditionsView(chatModel) + else -> {} + } + } + } +} + val ANDROID_CALL_TOP_PADDING = 40.dp @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index bc81c958e7..a745542602 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1687,6 +1687,18 @@ sealed class ChatInfo: SomeChat, NamedChat { else -> null } + val sendAsGroup: Boolean get() { + val g = (this as? Group)?.groupInfo + return if (g != null && g.useRelays && g.membership.memberRole >= GroupMemberRole.Owner) { + when (groupChatScope()) { + null -> true + is GroupChatScope.MemberSupport -> false + } + } else { + false + } + } + fun ntfsEnabled(ci: ChatItem): Boolean = ntfsEnabled(ci.meta.userMention) @@ -2133,6 +2145,7 @@ data class GroupInfo ( GroupFeature.SimplexLinks -> p.simplexLinks.on(membership) GroupFeature.Reports -> p.reports.on GroupFeature.History -> p.history.on + GroupFeature.Support -> p.support.on } } @@ -3800,8 +3813,8 @@ sealed class CIContent: ItemContent { is RcvBlocked -> generalGetString(MR.strings.blocked_by_admin_item_description) is SndDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) - is SndGroupE2EEInfo -> e2eeInfoNoPQStr - is RcvGroupE2EEInfo -> e2eeInfoNoPQStr + is SndGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo) + is RcvGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo) is ChatBanner -> "" is InvalidJSON -> "invalid data" } @@ -3838,6 +3851,9 @@ sealed class CIContent: ItemContent { private val e2eeInfoNoPQStr: String = generalGetString(MR.strings.e2ee_info_no_pq_short) + fun groupE2EEInfoStr(e2EEInfo: E2EEInfo): String = + if (e2EEInfo.public == true) generalGetString(MR.strings.e2ee_info_no_e2ee) else e2eeInfoNoPQStr + fun featureText(feature: Feature, enabled: String, param: Int?, role: GroupMemberRole? = null): String = (if (feature.hasParam) { "${feature.text}: ${timeText(param)}" @@ -4364,7 +4380,7 @@ enum class CIGroupInvitationStatus { } @Serializable -class E2EEInfo (val pqEnabled: Boolean?) {} +class E2EEInfo (val pqEnabled: Boolean?, val public: Boolean? = null) {} object MsgContentSerializer : KSerializer { override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index f62d4c262d..4d396c117e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -213,7 +213,7 @@ class AppPreferences { val shouldImportAppSettings = mkBoolPreference(SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS, false) val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME) - val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName) + val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.DARK.themeName) val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = { json.encodeToString(MapSerializer(String.serializer(), String.serializer()), it) }, decode = { @@ -5693,7 +5693,8 @@ enum class GroupFeature: Feature { @SerialName("files") Files, @SerialName("simplexLinks") SimplexLinks, @SerialName("reports") Reports, - @SerialName("history") History; + @SerialName("history") History, + @SerialName("support") Support; override val hasParam: Boolean get() = when(this) { TimedMessages -> true @@ -5711,10 +5712,12 @@ enum class GroupFeature: Feature { SimplexLinks -> true Reports -> false History -> false + Support -> false } - override val text: String - get() = when(this) { + override val text: String get() = text(isChannel = false) + + fun text(isChannel: Boolean): String = when(this) { TimedMessages -> generalGetString(MR.strings.timed_messages) DirectMessages -> generalGetString(MR.strings.direct_messages) FullDelete -> generalGetString(MR.strings.full_deletion) @@ -5722,8 +5725,9 @@ enum class GroupFeature: Feature { Voice -> generalGetString(MR.strings.voice_messages) Files -> generalGetString(MR.strings.files_and_media) SimplexLinks -> generalGetString(MR.strings.simplex_links) - Reports -> generalGetString(MR.strings.group_reports_member_reports) + Reports -> generalGetString(if (isChannel) MR.strings.group_reports_subscriber_reports else MR.strings.group_reports_member_reports) History -> generalGetString(MR.strings.recent_history) + Support -> generalGetString(MR.strings.chat_with_admins) } val icon: Painter @@ -5737,6 +5741,7 @@ enum class GroupFeature: Feature { SimplexLinks -> painterResource(MR.images.ic_link) Reports -> painterResource(MR.images.ic_flag) History -> painterResource(MR.images.ic_schedule) + Support -> painterResource(MR.images.ic_help) } @Composable @@ -5750,9 +5755,10 @@ enum class GroupFeature: Feature { SimplexLinks -> painterResource(MR.images.ic_link) Reports -> painterResource(MR.images.ic_flag_filled) History -> painterResource(MR.images.ic_schedule_filled) + Support -> painterResource(MR.images.ic_help_filled) } - fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String = + fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean, isChannel: Boolean = false): String = if (canEdit) { when(this) { TimedMessages -> when(enabled) { @@ -5760,8 +5766,8 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_disappearing) } DirectMessages -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_direct_messages) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_direct_messages) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.allow_direct_messages_channel else MR.strings.allow_direct_messages) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.prohibit_direct_messages_channel else MR.strings.prohibit_direct_messages) } FullDelete -> when(enabled) { GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_delete_messages) @@ -5788,47 +5794,55 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_member_reports) } History -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.enable_sending_recent_history_channel else MR.strings.enable_sending_recent_history) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.disable_sending_recent_history_channel else MR.strings.disable_sending_recent_history) + } + Support -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.allow_chat_with_admins_channel else MR.strings.allow_chat_with_admins) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_chat_with_admins) } } } else { when(this) { TimedMessages -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_disappearing) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_disappearing_channel else MR.strings.group_members_can_send_disappearing) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disappearing_messages_are_prohibited) } DirectMessages -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_dms) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.direct_messages_are_prohibited) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_dms_channel else MR.strings.group_members_can_send_dms) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.direct_messages_are_prohibited_channel else MR.strings.direct_messages_are_prohibited) } FullDelete -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_delete) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_delete_channel else MR.strings.group_members_can_delete) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.message_deletion_prohibited_in_chat) } Reactions -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_add_message_reactions) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_add_message_reactions_channel else MR.strings.group_members_can_add_message_reactions) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.message_reactions_are_prohibited) } Voice -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_voice) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_voice_channel else MR.strings.group_members_can_send_voice) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.voice_messages_are_prohibited) } Files -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_files) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_files_channel else MR.strings.group_members_can_send_files) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.files_are_prohibited_in_group) } SimplexLinks -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_simplex_links) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_simplex_links_channel else MR.strings.group_members_can_send_simplex_links) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.simplex_links_are_prohibited_in_group) } Reports -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_reports) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_reports_channel else MR.strings.group_members_can_send_reports) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.member_reports_are_prohibited) } History -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.recent_history_is_sent_to_new_members_channel else MR.strings.recent_history_is_sent_to_new_members) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.recent_history_is_not_sent_to_new_members_channel else MR.strings.recent_history_is_not_sent_to_new_members) + } + Support -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.members_can_chat_with_admins_channel else MR.strings.members_can_chat_with_admins) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.chat_with_admins_is_prohibited) } } } @@ -5955,6 +5969,7 @@ data class FullGroupPreferences( val simplexLinks: RoleGroupPreference, val reports: GroupPreference, val history: GroupPreference, + val support: GroupPreference, val commands: List, ) { fun toGroupPreferences(): GroupPreferences = @@ -5968,6 +5983,7 @@ data class FullGroupPreferences( simplexLinks = simplexLinks, reports = reports, history = history, + support = support, commands = commands, ) @@ -5982,6 +5998,7 @@ data class FullGroupPreferences( simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), reports = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON), + support = GroupPreference(GroupFeatureEnabled.ON), commands = listOf() ) } @@ -5998,6 +6015,7 @@ data class GroupPreferences( val simplexLinks: RoleGroupPreference? = null, val reports: GroupPreference? = null, val history: GroupPreference? = null, + val support: GroupPreference? = null, val commands: List? = null ) { companion object { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 36a7ae1a80..3805a8e8b7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -162,11 +162,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } else if (startChat().await()) { val savedOnboardingStage = appPreferences.onboardingStage.get() val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { - if (appPlatform.isAndroid) { - OnboardingStage.Step4_SetNotificationsMode - } else { - OnboardingStage.OnboardingComplete - } + OnboardingStage.Step4_NetworkCommitments } else { savedOnboardingStage } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt index 7decadb1b3..6df5eefbe3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt @@ -3,47 +3,16 @@ package chat.simplex.common.ui.theme import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.colorspace.ColorSpaces -import chat.simplex.common.views.helpers.mixWith import kotlin.math.cos -import kotlin.math.min -import kotlin.math.pow import kotlin.math.sin -/** Create a Display P3 Color from oklch components. H in degrees. */ fun oklch(L: Float, C: Float, H: Float, alpha: Float = 1f): Color { val hRad = H * (Math.PI.toFloat() / 180f) - val a = C * cos(hRad) - val b = C * sin(hRad) - // oklab → LMS (Ottosson 2021) - val l_ = L + 0.3963377774f * a + 0.2158037573f * b - val m_ = L - 0.1055613458f * a - 0.0638541728f * b - val s_ = L - 0.0894841775f * a - 1.2914855480f * b - val l = l_ * l_ * l_ - val m = m_ * m_ * m_ - val s = s_ * s_ * s_ - // LMS → linear Display P3 (direct, no sRGB clamping) - val r = 3.1281105148f * l - 2.2570749853f * m + 0.1293047593f * s - val g = -1.0911282009f * l + 2.4132668169f * m - 0.3221681599f * s - val bl = -0.0260136845f * l - 0.5080276339f * m + 1.5333166364f * s - // linear P3 → gamma-encoded P3 (same transfer function as sRGB) - fun gammaEncode(x: Float) = if (x >= 0.0031308f) 1.055f * x.pow(1f / 2.4f) - 0.055f else 12.92f * x - return Color( - red = gammaEncode(r.coerceIn(0f, 1f)), - green = gammaEncode(g.coerceIn(0f, 1f)), - blue = gammaEncode(bl.coerceIn(0f, 1f)), - alpha = alpha, - colorSpace = ColorSpaces.DisplayP3 - ) + return Color(L, C * cos(hRad), C * sin(hRad), alpha, ColorSpaces.Oklab) } -val Purple200 = Color(0xFFBB86FC) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val Gray = Color(0x22222222) val Indigo = Color(0xFF9966FF) val SimplexBlue = oklch(0.6320536f, 0.2017874f, 254.0879f) // If this value changes also need to update #0088ff in string resource files val SimplexGreen = oklch(0.7871495f, 0.1979258f, 146.6814f) // #ff4dda67 diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt index 9acfffb3ac..9b0f89c36d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt @@ -10,7 +10,7 @@ val Typography = Typography( h1 = TextStyle( fontFamily = Inter, fontWeight = FontWeight.Bold, - fontSize = 32.sp, + fontSize = 33.5.sp, ), h2 = TextStyle( fontFamily = Inter, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 6ec124048c..3e4b8ce1db 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -4,6 +4,7 @@ import SectionTextFooter import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors @@ -11,28 +12,44 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.* +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.ContentScale import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.migration.MigrateToDeviceView +import chat.simplex.common.views.migration.MigrationToState +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.onboarding.* +import chat.simplex.common.views.usersettings.DeleteImageButton +import chat.simplex.common.views.usersettings.EditImageButton import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +import java.net.URI const val MAX_BIO_LENGTH_BYTES = 160 @@ -46,18 +63,63 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { val scrollState = rememberScrollState() val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } - Box( - modifier = Modifier - .fillMaxSize() - .padding(top = 20.dp) - ) { - val displayName = rememberSaveable { mutableStateOf("") } - val shortDescr = rememberSaveable { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } + val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) + val displayName = rememberSaveable { mutableStateOf("") } + val shortDescr = rememberSaveable { mutableStateOf("") } + val chosenImage = rememberSaveable { mutableStateOf(null) } + val profileImage = rememberSaveable { mutableStateOf(null) } + val focusRequester = remember { FocusRequester() } + ModalBottomSheetLayout( + scrimColor = Color.Black.copy(alpha = 0.12F), + modifier = Modifier.imePadding(), + sheetContent = { + GetImageBottomSheet( + chosenImage, + onImageChange = { bitmap -> profileImage.value = resizeImageToStrSize(cropToSquare(bitmap), maxDataSize = 12500) }, + hideBottomSheet = { + scope.launch { bottomSheetModalState.hide() } + }) + }, + sheetState = bottomSheetModalState, + sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) + ) { + Box( + modifier = Modifier.fillMaxSize() + ) { ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING_HALF) + Row( + Modifier + .fillMaxWidth() + .padding(vertical = DEFAULT_PADDING_HALF), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier, + contentAlignment = Alignment.Center + ) { + Box(contentAlignment = Alignment.TopEnd) { + Box(contentAlignment = Alignment.Center) { + ProfileImage(128.dp, image = profileImage.value) + EditImageButton { scope.launch { bottomSheetModalState.show() } } + } + if (profileImage.value != null) { + DeleteImageButton { profileImage.value = null } + } + } + } + if (BuildConfigCommon.SIMPLEX_ASSETS) { + Image( + painterResource(if (isInDarkTheme()) MR.images.create_profile_light else MR.images.create_profile), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.height(140.dp) + ) + } + } Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.create_profile), withPadding = false, bottomPadding = DEFAULT_PADDING) Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text( stringResource(MR.strings.display_name), @@ -100,9 +162,9 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { iconColor = MaterialTheme.colors.primary, click = { if (chatModel.localUserCreated.value == true) { - createProfileInProfiles(chatModel, displayName.value, shortDescr.value, close) + createProfileInProfiles(chatModel, displayName.value, shortDescr.value, profileImage.value, close) } else { - createProfileInNoProfileSetup(displayName.value, close) + createProfileInNoProfileSetup(displayName.value, profileImage.value, close) } }, ) @@ -123,49 +185,119 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { } } } + } } @Composable fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { - val scope = rememberCoroutineScope() - val scrollState = rememberScrollState() - val keyboardState by getKeyboardState() - var savedKeyboardState by remember { mutableStateOf(keyboardState) } - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({ - if (chatModel.users.none { !it.user.hidden }) { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } else { - close() + if (appPlatform.isDesktop) { + CreateFirstProfileDesktop(chatModel, close) + } else { + CreateFirstProfileMobile(chatModel, close) + } +} + +@Composable +private fun RowScope.MigrateButton(refocusTrigger: MutableState) { + val focusManager = LocalFocusManager.current + TextButton( + onClick = { + focusManager.clearFocus() + if (chatModel.migrationState.value == null) { + chatModel.migrationState.value = MigrationToState.PasteOrScanLink } - }) { - ColumnWithScrollBar { - val displayName = rememberSaveable { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } - Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, end = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.create_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) - } - ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - Spacer(Modifier.height(DEFAULT_PADDING)) - ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - Spacer(Modifier.height(DEFAULT_PADDING)) - ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) + ModalManager.fullscreen.showCustomModal(animated = false, forceAnimated = appPlatform.isDesktop) { close -> + MigrateToDeviceView { + close() + refocusTrigger.value++ } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + } + }, + modifier = Modifier.padding(end = DEFAULT_PADDING_HALF) + ) { + Icon(painterResource(MR.images.ic_download), null, Modifier.size(22.dp), tint = MaterialTheme.colors.primary) + Spacer(Modifier.width(4.dp)) + Text( + stringResource(if (appPlatform.isDesktop) MR.strings.migrate_from_another_device else MR.strings.migrate), + color = MaterialTheme.colors.primary, fontWeight = FontWeight.Medium + ) + } +} + +private fun onboardingBackAction(chatModel: ChatModel, close: () -> Unit) { + if (chatModel.users.none { !it.user.hidden }) { + appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } else { + close() + } +} + +@Composable +private fun CreateFirstProfileMobile(chatModel: ChatModel, close: () -> Unit) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + val focusRequester = remember { FocusRequester() } + val refocusTrigger = remember { mutableStateOf(0) } + ModalView( + close = { onboardingBackAction(chatModel, close) }, + endButtons = { MigrateButton(refocusTrigger) } + ) { + val displayName = rememberSaveable { mutableStateOf("") } + val keyboardState by getKeyboardState() + val imageHeightModifier = if (keyboardState == KeyboardState.Opened) { + Modifier.heightIn(max = 100.dp) + } else { + Modifier + } + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally, maxIntrinsicSize = true) { + Spacer(Modifier.weight(1f)) + + OnboardingImage( + MR.images.your_profile, MR.images.your_profile_light, MR.images.ic_person, + modifier = Modifier + .then(if (keyboardState != KeyboardState.Opened) Modifier.fillMaxWidth() else Modifier) + .then(imageHeightModifier) + ) + + Text( + stringResource(MR.strings.onboarding_your_profile), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Text( + stringResource(MR.strings.onboarding_on_your_phone), + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.secondary, + lineHeight = 25.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 14.dp) + ) + Text( + stringResource(MR.strings.onboarding_no_account), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + ProfileNameField(displayName, stringResource(MR.strings.enter_profile_name), { it.trim() == mkValidName(it) }, focusRequester) + + Spacer(Modifier.weight(1f)) + + Column(Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( - if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.create_profile_button, + Modifier.fillMaxWidth(), + labelId = MR.strings.create_profile, onboarding = null, enabled = canCreateProfile(displayName.value), - onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) } + onclick = { createProfileOnboarding(chatModel, displayName.value, close) } ) - // Reserve space - TextButtonBelowOnboardingButton("", null) } - LaunchedEffect(Unit) { + LaunchedEffect(refocusTrigger.value) { delay(300) focusRequester.requestFocus() } @@ -173,21 +305,57 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { LaunchedEffect(Unit) { setLastVersionDefault(chatModel) } - if (savedKeyboardState != keyboardState) { - LaunchedEffect(keyboardState) { - scope.launch { - savedKeyboardState = keyboardState - scrollState.animateScrollTo(scrollState.maxValue) - } - } - } } } } -fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { +@Composable +private fun CreateFirstProfileDesktop(chatModel: ChatModel, close: () -> Unit) { + val focusRequester = remember { FocusRequester() } + val refocusTrigger = remember { mutableStateOf(0) } + val displayName = rememberSaveable { mutableStateOf("") } + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView( + close = { onboardingBackAction(chatModel, close) }, + endButtons = { MigrateButton(refocusTrigger) } + ) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Text(stringResource(MR.strings.onboarding_on_your_phone), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.onboarding_no_account, TextAlign.Center, style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.secondary)) + Spacer(Modifier.height(DEFAULT_PADDING)) + ProfileNameField(displayName, stringResource(MR.strings.enter_profile_name), { it.trim() == mkValidName(it) }, focusRequester) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + Modifier.widthIn(min = 300.dp), + labelId = MR.strings.create_profile, + onboarding = null, + enabled = canCreateProfile(displayName.value), + onclick = { createProfileOnboarding(chatModel, displayName.value, close) } + ) + TextButtonBelowOnboardingButton("", null) + } + } + LaunchedEffect(Unit) { + setLastVersionDefault(chatModel) + } + } + } + LaunchedEffect(refocusTrigger.value) { + delay(300) + focusRequester.requestFocus() + } +} + +fun createProfileInNoProfileSetup(displayName: String, image: String? = null, close: () -> Unit) { withBGApi { - val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, null)) ?: return@withBGApi + val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, image)) ?: return@withBGApi if (!chatModel.connectedToRemote()) { chatModel.localUserCreated.value = true } @@ -198,16 +366,16 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { } } -fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, close: () -> Unit) { +fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, image: String? = null, close: () -> Unit) { withBGApi { val rhId = chatModel.remoteHostId() val user = chatModel.controller.apiCreateActiveUser( - rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, null) + rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, image) ) ?: return@withBGApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) + chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments) } else { val users = chatModel.controller.listUsers(rhId) chatModel.users.clear() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 95f51bf284..51128646e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -419,9 +419,9 @@ fun ComposeView( withLongRunningApi(slow = 60_000) { if (wait != null) delay(wait) if (pendingLinkUrl.value != url) return@withLongRunningApi - if (chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.get() - && !chatModel.controller.appPrefs.networkUseSocksProxy.get()) { - showLinkPreviewsConfirmAlert { enable -> + if (chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.get()) { + val socksEnabled = chatModel.controller.appPrefs.networkUseSocksProxy.get() + showLinkPreviewsConfirmAlert(socksEnabled) { enable -> if (enable != null) { chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.set(false) chatModel.controller.appPrefs.privacyLinkPreviews.set(enable) @@ -534,7 +534,7 @@ fun ComposeView( type = cInfo.chatType, id = cInfo.apiId, scope = cInfo.groupChatScope(), - sendAsGroup = (cInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false, + sendAsGroup = cInfo.sendAsGroup, live = live, ttl = ttl, composedMessages = listOf(ComposedMessage(file, quoted, mc, mentions)) @@ -665,7 +665,7 @@ fun ComposeView( toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, toScope = chat.chatInfo.groupChatScope(), - sendAsGroup = (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false, + sendAsGroup = chat.chatInfo.sendAsGroup, fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, fromScope = fromChatInfo.groupChatScope(), @@ -1496,7 +1496,7 @@ fun ComposeView( ) is SharedContent.ChatLink -> { val cInfo = chat.chatInfo - val sendAsGroup = (cInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false + val sendAsGroup = cInfo.sendAsGroup withBGApi { val mc = chatModel.controller.apiShareChatMsgContent( chat.remoteHostId, ChatType.Group, shared.groupInfo.groupId, @@ -1693,7 +1693,7 @@ fun ComposeView( Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) { AttachmentAndCommandsButtons() val broadcastPlaceholder = (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.let { gi -> - if (gi.useRelays && gi.membership.memberRole >= GroupMemberRole.Owner) generalGetString(MR.strings.compose_view_broadcast) + if (gi.useRelays && gi.membership.memberRole >= GroupMemberRole.Owner && chat.chatInfo.groupChatScope() == null) generalGetString(MR.strings.compose_view_broadcast) else null } SendMsgView_(disableSendButton = disableSendButton, placeholder = broadcastPlaceholder) @@ -1703,10 +1703,15 @@ fun ComposeView( } } -private fun showLinkPreviewsConfirmAlert(onChoice: (Boolean?) -> Unit) { +private fun showLinkPreviewsConfirmAlert(socksEnabled: Boolean, onChoice: (Boolean?) -> Unit) { AlertManager.shared.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.link_previews_alert_title), - text = AnnotatedString(generalGetString(MR.strings.link_previews_alert_desc)), + text = AnnotatedString( + if (socksEnabled) + generalGetString(MR.strings.link_previews_alert_desc) + "\n\n" + generalGetString(MR.strings.link_previews_alert_desc_socks) + else + generalGetString(MR.strings.link_previews_alert_desc) + ), onDismissRequest = { onChoice(null) }, buttons = { Column { @@ -1714,13 +1719,13 @@ private fun showLinkPreviewsConfirmAlert(onChoice: (Boolean?) -> Unit) { AlertManager.shared.hideAlert() onChoice(false) }) { - Text(stringResource(MR.strings.link_previews_alert_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + Text(stringResource(MR.strings.link_previews_alert_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } SectionItemView({ AlertManager.shared.hideAlert() onChoice(true) }) { - Text(stringResource(MR.strings.link_previews_alert_enable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + Text(stringResource(MR.strings.link_previews_alert_enable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = if (socksEnabled) MaterialTheme.colors.primary else Color.Red) } // SectionItemView({ // AlertManager.shared.hideAlert() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 156ad2cd97..2c3a6c713b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -546,6 +546,10 @@ fun ModalData.GroupChatInfoLayout( var anyTopSectionRowShow = false val channelLink = groupInfo.groupProfile.publicGroup?.groupLink + val showUserSupportChat = groupInfo.membership.memberActive && + ((groupInfo.fullGroupPreferences.support.on && groupInfo.membership.memberRole < GroupMemberRole.Moderator) + || groupInfo.membership.supportChat != null) + if (groupInfo.useRelays) { SectionView { if (groupInfo.isOwner && groupLink != null) { @@ -564,6 +568,14 @@ fun ModalData.GroupChatInfoLayout( anyTopSectionRowShow = true ChannelMembersButton(chat.remoteHostId, groupInfo, showMemberInfo) } + if (groupInfo.membership.memberRole >= GroupMemberRole.Moderator) { + anyTopSectionRowShow = true + MemberSupportButton(chat, openMemberSupport) + } + if (showUserSupportChat) { + anyTopSectionRowShow = true + UserSupportChatButton(chat, groupInfo, scrollToItemId) + } } if (!groupInfo.isOwner && channelLink != null) { SectionTextFooter(stringResource(MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect)) @@ -590,10 +602,7 @@ fun ModalData.GroupChatInfoLayout( } } } - if ( - groupInfo.membership.memberActive && - (groupInfo.membership.memberRole < GroupMemberRole.Moderator || groupInfo.membership.supportChat != null) - ) { + if (showUserSupportChat) { anyTopSectionRowShow = true UserSupportChatButton(chat, groupInfo, scrollToItemId) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index fc5d697f4f..1bc6f038f3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -483,14 +483,15 @@ fun GroupMemberInfoLayout( SectionSpacer() } + val showMemberSupportChat = !openedFromSupportChat && + groupInfo.membership.memberRole >= GroupMemberRole.Moderator && + member.memberRole != GroupMemberRole.Relay && + ((groupInfo.fullGroupPreferences.support.on && member.memberRole < GroupMemberRole.Moderator) + || member.supportChat != null) + if (member.memberActive) { SectionView { - if ( - !openedFromSupportChat && - groupInfo.membership.memberRole >= GroupMemberRole.Moderator && - member.memberRole != GroupMemberRole.Relay && - (member.memberRole < GroupMemberRole.Moderator || member.supportChat != null) - ) { + if (showMemberSupportChat) { SupportChatButton() } if (connectionCode != null && !(groupInfo.useRelays && member.memberRole == GroupMemberRole.Relay)) { @@ -504,6 +505,11 @@ fun GroupMemberInfoLayout( // } } SectionDividerSpaced() + } else if (groupInfo.useRelays && member.memberCurrent && showMemberSupportChat) { + SectionView { + SupportChatButton() + } + SectionDividerSpaced() } if (member.contactLink != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 902b4c9828..740349eaea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -155,7 +155,7 @@ private fun GroupPreferencesLayout( } @Composable fun ReportsPreference() { val enableReports = remember(preferences) { mutableStateOf(preferences.reports.enable) } - FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> + FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated, disabled = true) { enable, _ -> // enable reports in 7.0 once directory support added applyPrefs(preferences.copy(reports = GroupPreference(enable = enable))) } } @@ -165,6 +165,16 @@ private fun GroupPreferencesLayout( applyPrefs(preferences.copy(history = GroupPreference(enable = enable))) } } + @Composable fun SupportPreference(disabled: Boolean = false, notice: String? = null, onEnable: ((() -> Unit) -> Unit)? = null) { + val enableSupport = remember(preferences) { mutableStateOf(preferences.support.enable) } + FeatureSection(GroupFeature.Support, enableSupport, null, groupInfo, preferences, onTTLUpdated, disabled = disabled, notice = notice) { enable, _ -> + applyPrefs(preferences.copy(support = GroupPreference(enable = enable))) + if (enable == GroupFeatureEnabled.ON) onEnable?.invoke { + enableSupport.value = GroupFeatureEnabled.OFF + applyPrefs(preferences.copy(support = GroupPreference(enable = GroupFeatureEnabled.OFF))) + } + } + } ColumnWithScrollBar { val titleId = if (groupInfo.useRelays) MR.strings.channel_preferences else if (groupInfo.businessChat == null) MR.strings.group_preferences @@ -192,6 +202,8 @@ private fun GroupPreferencesLayout( ReportsPreference() SectionDividerSpaced(true, maxBottomPadding = false) HistoryPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + SupportPreference(disabled = true) } else { TimedMessagesPreference() SectionDividerSpaced(true, maxBottomPadding = false) @@ -200,6 +212,17 @@ private fun GroupPreferencesLayout( ReactionsPreference() SectionDividerSpaced(true, maxBottomPadding = false) HistoryPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + SupportPreference(notice = generalGetString(MR.strings.chat_with_admins_relay_note), onEnable = { revert -> + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.enable_chats_with_admins_question), + text = generalGetString(MR.strings.chat_with_admins_relay_note), + confirmText = generalGetString(MR.strings.enable_chats_with_admins), + destructive = true, + onDismiss = revert, + onDismissRequest = revert, + ) + }) } if (groupInfo.isOwner) { SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) @@ -233,6 +256,8 @@ private fun FeatureSection( groupInfo: GroupInfo, preferences: FullGroupPreferences, onTTLUpdated: (Int?) -> Unit, + disabled: Boolean = false, + notice: String? = null, onSelected: (GroupFeatureEnabled, GroupMemberRole?) -> Unit ) { SectionView { @@ -242,10 +267,10 @@ private fun FeatureSection( val timedOn = feature == GroupFeature.TimedMessages && enableFeature.value == GroupFeatureEnabled.ON if (groupInfo.isOwner) { PreferenceToggleWithIcon( - feature.text, + feature.text(groupInfo.isChannel), icon, iconTint, - disabled = feature == GroupFeature.Reports, // remove in 6.4 + disabled = disabled, checked = enableFeature.value == GroupFeatureEnabled.ON, ) { checked -> onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value) @@ -274,7 +299,7 @@ private fun FeatureSection( } } else { InfoRow( - feature.text, + feature.text(groupInfo.isChannel), enableFeature.value.text, icon = icon, iconTint = iconTint, @@ -292,7 +317,10 @@ private fun FeatureSection( onSelected(enableFeature.value, null) } } - SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner)) + SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner, groupInfo.isChannel)) + if (notice != null) { + SectionTextFooter(notice) + } } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt index c3cf954ab6..3d76c845ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt @@ -116,7 +116,10 @@ private fun ModalData.MemberSupportViewLayout( if (membersWithChats.isEmpty()) { item { Box(Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING), contentAlignment = Alignment.Center) { - Text(generalGetString(MR.strings.no_support_chats), color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center) + Text( + generalGetString(if (groupInfo.fullGroupPreferences.support.on) MR.strings.no_support_chats else MR.strings.support_chats_disabled), + color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center + ) } } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 14afccee54..15b0a12822 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -680,21 +680,17 @@ fun ChatItemView( } @Composable - fun E2EEInfoNoPQText() { - e2eeInfoText(MR.strings.e2ee_info_no_pq) + fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) { + e2eeInfoText(when (e2EEInfo.pqEnabled) { + true -> MR.strings.e2ee_info_pq + false -> MR.strings.e2ee_info_no_pq + null -> MR.strings.e2ee_info_e2ee + }) } @Composable - fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) { - if (e2EEInfo.pqEnabled != null) { - if (e2EEInfo.pqEnabled) { - e2eeInfoText(MR.strings.e2ee_info_pq) - } else { - E2EEInfoNoPQText() - } - } else { - e2eeInfoText(MR.strings.e2ee_info_e2ee) - } + fun GroupE2EEInfoText(e2EEInfo: E2EEInfo) { + e2eeInfoText(if (e2EEInfo.public == true) MR.strings.e2ee_info_no_e2ee else MR.strings.e2ee_info_no_pq) } if (cItem.meta.itemDeleted != null && (!revealed.value || cItem.isDeletedContent)) { @@ -794,8 +790,8 @@ fun ChatItemView( is CIContent.RcvBlocked -> DeletedItem() is CIContent.SndDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) - is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText() - is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText() + is CIContent.SndGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo) + is CIContent.RcvGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo) is CIContent.ChatBanner -> Spacer(modifier = Modifier.size(0.dp)) is CIContent.InvalidJSON -> { CIInvalidJSONView(c.json) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 8f836c3c29..01dcd021f7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -90,41 +90,85 @@ private fun showNewChatSheet(oneHandUI: State) { @Composable fun ToggleChatListCard() { - ChatListCard( - close = { - appPrefs.oneHandUICardShown.set(true) - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.one_hand_ui), - text = generalGetString(MR.strings.one_hand_ui_change_instruction), + val oneHandUI = remember { appPrefs.oneHandUI.state } + val onClose = { + appPrefs.oneHandUICardShown.set(true) + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.one_hand_ui), + text = generalGetString(MR.strings.one_hand_ui_change_instruction), + ) + } + val activeBg = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) + .copy(alpha = appPrefs.inAppBarsAlpha.get()) + val selectedBg = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.92f) + Row( + Modifier + .padding(horizontal = 16.dp, vertical = 12.dp) + .fillMaxWidth() + .height(IntrinsicSize.Min) + .clip(RoundedCornerShape(percent = 50)), + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + ToolbarSegment( + icon = MR.images.ic_mobile_3, + text = stringResource(MR.strings.one_hand_ui_bottom_bar), + isSelected = oneHandUI.value, + selectedBg = selectedBg, + activeBg = activeBg, + modifier = Modifier.weight(1f) + ) { appPrefs.oneHandUI.set(true) } + Box(Modifier.weight(1f).fillMaxHeight()) { + ToolbarSegment( + icon = MR.images.ic_mobile_4, + text = stringResource(MR.strings.one_hand_ui_top_bar), + isSelected = !oneHandUI.value, + selectedBg = selectedBg, + activeBg = activeBg, + modifier = Modifier.fillMaxSize() + ) { appPrefs.oneHandUI.set(false) } + Icon( + painterResource(MR.images.ic_close), null, + Modifier + .align(Alignment.CenterEnd) + .padding(end = 4.dp) + .clip(CircleShape) + .clickable(onClick = onClose) + .padding(8.dp) + .size(16.dp), + tint = MaterialTheme.colors.secondary ) } + } +} + +@Composable +private fun ToolbarSegment( + icon: ImageResource, + text: String, + isSelected: Boolean, + selectedBg: Color, + activeBg: Color, + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + Row( + modifier + .fillMaxHeight() + .background(if (isSelected) selectedBg else activeBg) + .then(if (!isSelected) Modifier.clickable(onClick = onClick) else Modifier) + .padding(start = 16.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically ) { - Column( - modifier = Modifier - .padding(horizontal = DEFAULT_PADDING) - .padding(top = DEFAULT_PADDING) - ) { - Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(MR.strings.one_hand_ui_card_title), style = MaterialTheme.typography.h3) - } - Row( - Modifier.fillMaxWidth().padding(top = 6.dp, bottom = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text(stringResource(MR.strings.one_hand_ui), Modifier.weight(10f), style = MaterialTheme.typography.body1) - - Spacer(Modifier.fillMaxWidth().weight(1f)) - - SharedPreferenceToggle( - appPrefs.oneHandUI, - enabled = true - ) - } - } + Icon( + painterResource(icon), null, Modifier.size(20.dp), + tint = if (isSelected) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + Spacer(Modifier.width(8.dp)) + Text( + text, + color = if (isSelected) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground, + style = MaterialTheme.typography.body1 + ) } } @@ -307,7 +351,7 @@ private fun ConnectBannerCard() { painterResource(if (isDark) MR.images.banner_create_link_light else MR.images.banner_create_link), contentDescription = null, contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().aspectRatio(BANNER_IMAGE_RATIO) ) } else { BannerGradientBox(isDark) { @@ -338,7 +382,7 @@ private fun ConnectBannerCard() { painterResource(if (isDark) MR.images.banner_paste_link_light else MR.images.banner_paste_link), contentDescription = null, contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().aspectRatio(BANNER_IMAGE_RATIO) ) } else { BannerGradientBox(isDark) { @@ -918,13 +962,18 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat } } } + if (!oneHandUICardShown.value) { + item { + ToggleChatListCard() + } + } itemsIndexed(chats, key = { _, chat -> chat.remoteHostId to chat.id }) { index, chat -> val nextChatSelected = remember(chat.id, chats) { derivedStateOf { chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value } } ChatListNavLinkView(chat, nextChatSelected) } - if (!oneHandUICardShown.value || !addressCreationCardShown.value) { + if (!addressCreationCardShown.value) { item { ChatListFeatureCards() } @@ -989,20 +1038,12 @@ private fun NoChatsView(searchText: MutableState) { @Composable private fun ChatListFeatureCards() { - val oneHandUI = remember { appPrefs.oneHandUI.state } - val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state } val addressCreationCardShown = remember { appPrefs.addressCreationCardShown.state } - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - if (!oneHandUICardShown.value && !oneHandUI.value) { - ToggleChatListCard() - } - if (!addressCreationCardShown.value && hasConversations(chatModel.chats.value)) { + if (!addressCreationCardShown.value && hasConversations(chatModel.chats.value)) { + Column(modifier = Modifier.padding(16.dp)) { ConnectBannerCard() } - if (!oneHandUICardShown.value && oneHandUI.value) { - ToggleChatListCard() - } } } @@ -1144,7 +1185,7 @@ private fun ExpandedTagFilterView(tag: PresetTagKind) { is ActiveFilter.PresetTag -> af.tag == tag else -> false } - val (icon, text) = presetTagLabel(tag, active) + val (icon, menuIcon, text) = presetTagLabel(tag, active) val color = if (active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary Row( @@ -1164,7 +1205,7 @@ private fun ExpandedTagFilterView(tag: PresetTagKind) { horizontalArrangement = Arrangement.Center ) { Icon( - painterResource(icon), + painterResource(menuIcon ?: icon), stringResource(text), Modifier.size(18.sp.toDp()), tint = color @@ -1206,9 +1247,9 @@ private fun CollapsedTagsFilterView(searchText: MutableState) { contentAlignment = Alignment.Center ) { if (selectedPresetTag != null) { - val (icon, text) = presetTagLabel(selectedPresetTag, true) + val (icon, menuIcon, text) = presetTagLabel(selectedPresetTag, true) Icon( - painterResource(icon), + painterResource(menuIcon ?: icon), stringResource(text), Modifier.size(18.sp.toDp()), tint = MaterialTheme.colors.primary @@ -1254,7 +1295,7 @@ fun ItemPresetFilterAction( showMenu: MutableState, onCloseMenuAction: MutableState<(() -> Unit)> ) { - val (icon, text) = presetTagLabel(presetTag, active) + val (icon, _, text) = presetTagLabel(presetTag, active) ItemAction( stringResource(text), painterResource(icon), @@ -1336,15 +1377,15 @@ fun presetTagMatchesChat(tag: PresetTagKind, chatInfo: ChatInfo, chatStats: Chat } } -private fun presetTagLabel(tag: PresetTagKind, active: Boolean): Pair = +private fun presetTagLabel(tag: PresetTagKind, active: Boolean): Triple = when (tag) { - PresetTagKind.GROUP_REPORTS -> (if (active) MR.images.ic_flag_filled else MR.images.ic_flag) to MR.strings.chat_list_group_reports - PresetTagKind.FAVORITES -> (if (active) MR.images.ic_star_filled else MR.images.ic_star) to MR.strings.chat_list_favorites - PresetTagKind.CONTACTS -> (if (active) MR.images.ic_person_filled else MR.images.ic_person) to MR.strings.chat_list_contacts - PresetTagKind.GROUPS -> (if (active) MR.images.ic_group_filled else MR.images.ic_group) to MR.strings.chat_list_groups - PresetTagKind.CHANNELS -> (if (active) MR.images.ic_bigtop_updates_circle_filled else MR.images.ic_bigtop_updates) to MR.strings.chat_list_channels - PresetTagKind.BUSINESS -> (if (active) MR.images.ic_work_filled else MR.images.ic_work) to MR.strings.chat_list_businesses - PresetTagKind.NOTES -> (if (active) MR.images.ic_folder_closed_filled else MR.images.ic_folder_closed) to MR.strings.chat_list_notes + PresetTagKind.GROUP_REPORTS -> Triple(if (active) MR.images.ic_flag_filled else MR.images.ic_flag, null, MR.strings.chat_list_group_reports) + PresetTagKind.FAVORITES -> Triple(if (active) MR.images.ic_star_filled else MR.images.ic_star, null, MR.strings.chat_list_favorites) + PresetTagKind.CONTACTS -> Triple(if (active) MR.images.ic_person_filled else MR.images.ic_person, null, MR.strings.chat_list_contacts) + PresetTagKind.GROUPS -> Triple(if (active) MR.images.ic_group_filled else MR.images.ic_group, null, MR.strings.chat_list_groups) + PresetTagKind.CHANNELS -> Triple(if (active) MR.images.ic_bigtop_updates_circle_filled else MR.images.ic_bigtop_updates, MR.images.ic_bigtop_updates, MR.strings.chat_list_channels) + PresetTagKind.BUSINESS -> Triple(if (active) MR.images.ic_work_filled else MR.images.ic_work, null, MR.strings.chat_list_businesses) + PresetTagKind.NOTES -> Triple(if (active) MR.images.ic_folder_closed_filled else MR.images.ic_folder_closed, null, MR.strings.chat_list_notes) } private fun presetCanBeCollapsed(tag: PresetTagKind): Boolean = when (tag) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt index afb557cc78..ee63846657 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt @@ -22,7 +22,10 @@ fun AppBarTitle( hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp, - enableAlphaChanges: Boolean = true + enableAlphaChanges: Boolean = true, + overrideTitleColor: Color? = null, + textAlign: TextAlign = TextAlign.Start, + lineHeight: TextUnit = TextUnit.Unspecified ) { val handler = LocalAppBarHandler.current val connection = if (enableAlphaChanges) handler?.connection else null @@ -34,10 +37,12 @@ fun AppBarTitle( } } val theme = CurrentColors.collectAsState() - val titleColor = MaterialTheme.appColors.title - val brush = if (theme.value.base == DefaultTheme.SIMPLEX) + val titleColor = overrideTitleColor ?: MaterialTheme.appColors.title + val brush = if (overrideTitleColor != null) + Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) + else if (theme.value.base == DefaultTheme.SIMPLEX) Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) - else // color is not updated when changing themes if I pass null here + else Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) Column { Text( @@ -48,9 +53,9 @@ fun AppBarTitle( alpha = bottomTitleAlpha(connection) }, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h1.copy(brush = brush), + style = MaterialTheme.typography.h1.copy(brush = brush, lineHeight = lineHeight), color = MaterialTheme.colors.primaryVariant, - textAlign = TextAlign.Start + textAlign = textAlign ) if (hostDevice != null) { Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt index 9c529e547a..d2a98ae101 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.LinkPreview +import chat.simplex.common.model.NetworkProxyAuth import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.chatViewScrollState @@ -24,67 +25,124 @@ import chat.simplex.common.views.chat.item.CHAT_IMAGE_LAYOUT_ID import chat.simplex.common.views.chat.item.imageViewFullWidth import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.jsoup.Jsoup +import java.net.Authenticator +import java.net.InetSocketAddress +import java.net.PasswordAuthentication +import java.net.Proxy import java.net.URL +import java.util.UUID private const val OG_SELECT_QUERY = "meta[property^=og:]" private const val ICON_SELECT_QUERY = "link[rel^=icon],link[rel^=apple-touch-icon],link[rel^=shortcut icon]" private val IMAGE_SUFFIXES = listOf(".jpg", ".png", ".ico", ".webp", ".gif") +// Authenticator.setDefault is process-global. The mutex serializes preview fetches +// so concurrent calls cannot clobber each other's authenticator, and so the +// snapshot/restore in getLinkPreview is race-free. +private val previewMutex = Mutex() + suspend fun getLinkPreview(url: String): LinkPreview? { return withContext(Dispatchers.IO) { - try { - val title: String? - val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withContext null - var imageUri = when { - IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> { - title = u.path.substringAfterLast("/") - url - } - else -> { - val connection = Jsoup.connect(url) - .ignoreContentType(true) - .timeout(10000) - .followRedirects(true) - - val response = if (url.lowercase().startsWith("https://x.com/")) { - // Apple sends request with special user-agent which handled differently by X.com. - // Different response that includes video poster from post - connection - .userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0") - .execute() - } else { - connection - .execute() - } - val doc = response.parse() - val ogTags = doc.select(OG_SELECT_QUERY) - title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title() - ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content") - ?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href") - } - } - if (imageUri != null) { - imageUri = normalizeImageUri(u, imageUri) + previewMutex.withLock { + try { try { - val stream = URL(imageUri).openStream() - val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000) - // TODO add once supported in iOS - // val description = ogTags.firstOrNull { - // it.attr("property") == "og:description" - // }?.attr("content") ?: "" - if (title != null) { - return@withContext LinkPreview(url, title, description = "", image) + val title: String? + val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withLock null + val useSocksProxy = appPrefs.networkUseSocksProxy.get() + val proxy: Proxy? + if (useSocksProxy) { + val networkProxy = appPrefs.networkProxy.get() + proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress(networkProxy.host, networkProxy.port)) + val (authUser, authPass) = when (networkProxy.auth) { + NetworkProxyAuth.USERNAME -> + if (networkProxy.username.isNotEmpty() && networkProxy.password.isNotEmpty()) + networkProxy.username to networkProxy.password + else + null to null + // Per-call random credentials drive Tor-style stream isolation: each + // preview gets its own circuit, and previews don't share a circuit + // with other unauthenticated traffic on the proxy. + NetworkProxyAuth.ISOLATE -> + UUID.randomUUID().toString() to UUID.randomUUID().toString() + } + if (authUser != null && authPass != null) { + Authenticator.setDefault(object : Authenticator() { + override fun getPasswordAuthentication(): PasswordAuthentication? = + // Only respond when the SOCKS proxy itself challenges. A destination + // server returning 401 also triggers RequestorType.SERVER; without + // this gate, the JDK's auto-retry would post our SOCKS credentials + // in an Authorization header to the destination. + if (requestingHost == networkProxy.host && requestingPort == networkProxy.port) + PasswordAuthentication(authUser, authPass.toCharArray()) + else null + }) + } else { + Authenticator.setDefault(null) + } + } else { + proxy = null + Authenticator.setDefault(null) + } + var imageUri = when { + IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> { + title = u.path.substringAfterLast("/") + url + } + else -> { + val connection = Jsoup.connect(url) + .ignoreContentType(true) + .timeout(10000) + .followRedirects(true) + .proxy(proxy) + + val response = if (url.lowercase().startsWith("https://x.com/")) { + // Apple sends request with special user-agent which handled differently by X.com. + // Different response that includes video poster from post + connection + .userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0") + .execute() + } else { + connection + .execute() + } + val doc = response.parse() + val ogTags = doc.select(OG_SELECT_QUERY) + title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title() + ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content") + ?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href") + } + } + if (imageUri != null) { + imageUri = normalizeImageUri(u, imageUri) + try { + val conn = URL(imageUri).openConnection(proxy ?: Proxy.NO_PROXY) + conn.connectTimeout = 20_000 + conn.readTimeout = 20_000 + val stream = conn.getInputStream() + val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000) + // TODO add once supported in iOS + // val description = ogTags.firstOrNull { + // it.attr("property") == "og:description" + // }?.attr("content") ?: "" + if (title != null) { + return@withLock LinkPreview(url, title, description = "", image) + } + } catch (e: Exception) { + e.printStackTrace() + } } } catch (e: Exception) { e.printStackTrace() } + return@withLock null + } finally { + Authenticator.setDefault(null) } - } catch (e: Exception) { - e.printStackTrace() } - return@withContext null } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 21520f5424..28c81fbf56 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -111,8 +111,8 @@ class ModalManager(private val placement: ModalPlacement? = null) { fun isLastModalOpen(id: ModalViewId): Boolean = modalViews.lastOrNull()?.id == id - fun showModal(settings: Boolean = false, showClose: Boolean = true, id: ModalViewId? = null, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { - showCustomModal(id = id) { close -> + fun showModal(settings: Boolean = false, showClose: Boolean = true, id: ModalViewId? = null, forceAnimated: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { + showCustomModal(id = id, forceAnimated = forceAnimated) { close -> ModalView(close, showClose = showClose, endButtons = endButtons, content = { content() }) } } @@ -123,7 +123,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { } } - fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, id: ModalViewId? = null, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { + fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, id: ModalViewId? = null, forceAnimated: Boolean = false, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { Log.d(TAG, "ModalManager.showCustomModal") val data = ModalData(keyboardCoversBar = keyboardCoversBar) // Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen. @@ -133,7 +133,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { } // Make animated appearance only on Android (everytime) and on Desktop (when it's on the start part of the screen or modals > 0) // to prevent unneeded animation on different situations - val anim = if (appPlatform.isAndroid) animated else animated && (modalCount.value > 0 || placement == ModalPlacement.START) + val anim = if (appPlatform.isAndroid) animated else (animated && (modalCount.value > 0 || placement == ModalPlacement.START)) || forceAnimated modalViews.add(ModalViewHolder(id, anim, data, modal)) _modalCount.value = modalViews.size - toRemove.size diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index c4821d1a20..424d500085 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -537,6 +537,20 @@ fun UriHandler.openUriCatching(uri: String) { } } +fun UriHandler.openExternalLink(uri: String) { + val uriHandler = this + if (uri.startsWith("https://simplex.chat/contact#") || (uri.startsWith("https://smp") && ".simplex.im/a#" in uri)) { + uriHandler.openVerifiedSimplexUri(uri) + } else { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.open_external_link_title), + text = uri, + confirmText = generalGetString(MR.strings.open_verb), + onConfirm = { uriHandler.openUriCatching(uri) } + ) + } +} + fun IntSize.Companion.Saver(): Saver = Saver( save = { it.width to it.height }, restore = { IntSize(it.first, it.second) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt index e60fcfc921..941d232776 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt @@ -23,6 +23,8 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.getUserServers import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import androidx.compose.ui.layout.ContentScale +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.views.* import chat.simplex.common.views.chat.group.GroupLinkView import chat.simplex.common.views.chatlist.openGroupChat @@ -110,7 +112,10 @@ fun AddChannelView(chatModel: ChatModel, close: () -> Unit, closeAll: () -> Unit fullName = "", shortDescr = null, image = profileImage.value, - groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON)) + groupPreferences = GroupPreferences( + history = GroupPreference(GroupFeatureEnabled.ON), + support = GroupPreference(GroupFeatureEnabled.OFF) + ) ) creationInProgress.value = true withBGApi { @@ -254,22 +259,37 @@ private fun ProfileStepView( ) { ModalView(close = close) { ColumnWithScrollBar { - AppBarTitle(generalGetString(MR.strings.create_channel_title)) - Box( + AppBarTitle(generalGetString(MR.strings.create_channel_title), bottomPadding = DEFAULT_PADDING_HALF) + Row( Modifier .fillMaxWidth() - .padding(bottom = 24.dp), - contentAlignment = Alignment.Center + .padding(vertical = DEFAULT_PADDING_HALF), + horizontalArrangement = if (BuildConfigCommon.SIMPLEX_ASSETS) Arrangement.SpaceEvenly else Arrangement.Center, + verticalAlignment = Alignment.CenterVertically ) { - Box(contentAlignment = Alignment.TopEnd) { - Box(contentAlignment = Alignment.Center) { - ProfileImage(108.dp, image = profileImage.value, icon = MR.images.ic_bigtop_updates_circle_filled) - EditImageButton { scope.launch { bottomSheetModalState.show() } } - } - if (profileImage.value != null) { - DeleteImageButton { profileImage.value = null } + // Padding offsets transparent space built into 3D asset + Box( + modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier, + contentAlignment = Alignment.Center + ) { + Box(contentAlignment = Alignment.TopEnd) { + Box(contentAlignment = Alignment.Center) { + ProfileImage(128.dp, image = profileImage.value, icon = MR.images.ic_bigtop_updates_circle_filled) + EditImageButton { scope.launch { bottomSheetModalState.show() } } + } + if (profileImage.value != null) { + DeleteImageButton { profileImage.value = null } + } } } + if (BuildConfigCommon.SIMPLEX_ASSETS) { + Image( + painterResource(if (isInDarkTheme()) MR.images.create_channel_light else MR.images.create_channel), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.height(140.dp) + ) + } } Row( Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), @@ -344,16 +364,23 @@ private fun ProgressStepView( val activeCount = groupRelays.value.count { it.relayStatus == RelayStatus.RsActive && relayMemberConnFailed(chatModel, it) == null } val total = groupRelays.value.size + fun showCancelAlert() { + val active = groupRelays.value.count { it.relayStatus == RelayStatus.RsActive && relayMemberConnFailed(chatModel, it) == null } + val tot = groupRelays.value.size + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.cancel_creating_channel_question), + text = String.format(generalGetString(MR.strings.cancel_channel_alert_msg), gInfo.groupProfile.displayName, active, tot), + confirmText = generalGetString(MR.strings.cancel_verb), + onConfirm = cancelChannelCreation, + dismissText = generalGetString(MR.strings.wait_verb), + destructive = true, + ) + } + if (appPlatform.isDesktop) { DisposableEffect(Unit) { chatModel.centerPanelBackgroundClickHandler = { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.cancel_creating_channel_question), - confirmText = generalGetString(MR.strings.cancel_creating_channel_confirm), - onConfirm = cancelChannelCreation, - dismissText = generalGetString(MR.strings.wait_verb), - destructive = true, - ) + showCancelAlert() true } onDispose { @@ -375,11 +402,11 @@ private fun ProgressStepView( } ModalView( - close = cancelChannelCreation, + close = { showCancelAlert() }, showClose = false, endButtons = { - TextButton(onClick = cancelChannelCreation) { - Text(generalGetString(MR.strings.cancel_verb)) + TextButton(onClick = { showCancelAlert() }) { + Text(generalGetString(MR.strings.button_delete_channel)) } } ) { @@ -457,7 +484,7 @@ private fun ProgressStepView( val enabled = activeCount > 0 SettingsActionItem( painterResource(MR.images.ic_link), - generalGetString(MR.strings.channel_link), + generalGetString(MR.strings.continue_to_next_step), click = { if (activeCount >= total) { onLinkReady() @@ -479,7 +506,7 @@ private fun ProgressStepView( AlertManager.shared.hideAlert() onLinkReady() }) { - Text(generalGetString(MR.strings.proceed_verb)) + Text(generalGetString(MR.strings.continue_to_next_step)) } } } @@ -488,7 +515,7 @@ private fun ProgressStepView( AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.not_all_relays_connected), text = alertText, - confirmText = generalGetString(MR.strings.proceed_verb), + confirmText = generalGetString(MR.strings.continue_to_next_step), onConfirm = { onLinkReady() } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index fa27672270..a54d2e42e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -109,7 +109,11 @@ fun AddGroupLayout( horizontalArrangement = if (BuildConfigCommon.SIMPLEX_ASSETS) Arrangement.SpaceEvenly else Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { - Box(contentAlignment = Alignment.Center) { + // Padding offsets transparent space built into 3D asset + Box( + modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier, + contentAlignment = Alignment.Center + ) { Box(contentAlignment = Alignment.TopEnd) { Box(contentAlignment = Alignment.Center) { ProfileImage(128.dp, image = profileImage.value, icon = MR.images.ic_supervised_user_circle_filled) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index b38fbf9f51..cafad97574 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -604,6 +604,7 @@ fun showPrepareContactAlert( confirmText = generalGetString(MR.strings.connect_plan_open_new_chat), onConfirm = { AlertManager.privacySensitive.hideAlert() + ModalManager.closeAllModalsEverywhere() withBGApi { val chat = chatModel.controller.apiPrepareContact(rhId, connectionLink, contactShortLinkData) if (chat != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt index 98954eb74f..26007c74af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.colorspace.ColorSpaces import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.layout @@ -89,17 +90,19 @@ internal fun gradientPoints(aspectRatio: Float, scale: Float): GradientEndpoints } internal val lightStops = arrayOf( - 0.0f to Color(0xFFd2e8ff), - 0.5f to Color(0xFFcce9ff), - 0.9f to Color(0xFFdfffff), - 1.0f to Color(0xFFfffcea) + 0.0f to oklch(0.9219f, 0.0431f, 249.4f), + 0.5f to oklch(0.9198f, 0.0471f, 240.7f), + 0.9f to oklch(0.9772f, 0.0358f, 196.6f), + 0.95f to oklch(0.9829f, 0.0104f, 70.0f), + 1.0f to oklch(0.9886f, 0.0272f, 99.1f) ) internal val darkStops = arrayOf( - 0.4f to Color(0xFF040a24), - 0.72f to Color(0xFF3854ab), - 0.9f to Color(0xFFa8edf3), - 1.0f to Color(0xFFfff6e0) + 0.4f to oklch(0.1578f, 0.0609f, 267.3f), + 0.72f to oklch(0.4729f, 0.1574f, 267.3f), + 0.9f to oklch(0.9024f, 0.0760f, 202.8f), + 0.95f to oklch(0.9384f, 0.0354f, 65.0f), + 1.0f to oklch(0.9744f, 0.0370f, 88.4f) ) private fun Modifier.maxHeightByWidthRatio(ratio: Float) = layout { measurable, constraints -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 9c6c0fa635..2fd77b46a1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -14,80 +14,160 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.TextStyle - import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.usersettings.networkAndServers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -fun ModalData.OnboardingConditionsView() { +fun OnboardingConditionsView(chatModel: ChatModel) { LaunchedEffect(Unit) { prepareChatBeforeFinishingOnboarding() } - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false) { - val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } - val selectedOperatorIds = remember { stateGetOrPut("selectedOperatorIds") { serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() } } - ColumnWithScrollBar( - Modifier - .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), - maxIntrinsicSize = true - ) { - Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), bottomPadding = DEFAULT_PADDING) - } + val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } + val selectedOperatorIds = remember { + mutableStateOf(OnboardingSharedState.selectedOperatorIds.ifEmpty { + serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() + }) + } - Spacer(Modifier.weight(1f)) - Column( - (if (appPlatform.isDesktop) Modifier.width(450.dp).align(Alignment.CenterHorizontally) else Modifier) - .fillMaxWidth() - .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), - horizontalAlignment = Alignment.Start - ) { - Text( - stringResource(MR.strings.onboarding_conditions_private_chats_not_accessible), - style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp) - ) - Spacer(Modifier.height(DEFAULT_PADDING)) - Text( - stringResource(MR.strings.onboarding_conditions_by_using_you_agree), - style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp) - ) - Spacer(Modifier.height(DEFAULT_PADDING)) - Text( - stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), - style = TextStyle(fontSize = 17.sp), - color = MaterialTheme.colors.primary, - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null + if (appPlatform.isDesktop) { + OnboardingConditionsDesktop(selectedOperatorIds) + } else { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, showAppBar = false) { + OnboardingShrinkingLayout( + modifier = Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer) + .systemBarsPadding() + .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), + topPadding = DEFAULT_PADDING, + image = { + Column(Modifier.padding(vertical = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingImage( + MR.images.network_commitments, MR.images.network_commitments_light, MR.images.ic_shield, + modifier = Modifier.fillMaxWidth(), + aspectRatio = 1.5f + ) + } + }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + stringResource(MR.strings.onboarding_network_commitments), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 42.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Column( + Modifier.fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING_HALF) + .padding(top = DEFAULT_PADDING), + horizontalAlignment = Alignment.Start ) { - ModalManager.fullscreen.showModal(endButtons = { ConditionsLinkButton() }) { - SimpleConditionsView(rhId = null) - } + Text( + stringResource(MR.strings.onboarding_conditions_private_chats_not_accessible), + style = MaterialTheme.typography.body2, + lineHeight = 22.sp + ) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_by_using_you_agree), + style = MaterialTheme.typography.body2, + lineHeight = 22.sp + ) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + ModalManager.fullscreen.showModal(endButtons = { ConditionsLinkButton() }) { + SimpleConditionsView(rhId = null) { + ModalManager.fullscreen.closeModal() + acceptConditions(selectedOperatorIds.value) + } + } + } + ) } - ) - } - Spacer(Modifier.weight(1f)) - - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds) - TextButtonBelowOnboardingButton(stringResource(MR.strings.onboarding_conditions_configure_server_operators)) { - ModalManager.fullscreen.showModalCloseable { close -> - ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + }, + button = { + Column(Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2), horizontalAlignment = Alignment.CenterHorizontally) { + AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds) } } + ) + } + } + } +} + +@Composable +private fun OnboardingConditionsDesktop(selectedOperatorIds: MutableState>) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_network_commitments), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Column(Modifier.width(450.dp), horizontalAlignment = Alignment.Start) { + ReadableText(MR.strings.onboarding_conditions_private_chats_not_accessible, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.onboarding_conditions_by_using_you_agree, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + ModalManager.fullscreen.showModal(forceAnimated = true, endButtons = { ConditionsLinkButton() }) { + SimpleConditionsView(rhId = null) { + ModalManager.fullscreen.closeModal() + acceptConditions(selectedOperatorIds.value) + } + } + } + ) + } + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds) + TextButtonBelowOnboardingButton("", null) } } } @@ -104,7 +184,7 @@ fun ModalData.ChooseServerOperators( prepareChatBeforeFinishingOnboarding() } CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false) { + ModalView(close, enableClose = selectedOperatorIds.value.isNotEmpty()) { ColumnWithScrollBar( Modifier .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), @@ -141,11 +221,9 @@ fun ModalData.ChooseServerOperators( } Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { val enabled = selectedOperatorIds.value.isNotEmpty() SetOperatorsButton(enabled, close) - // Reserve space - TextButtonBelowOnboardingButton("", null) } } } @@ -212,52 +290,42 @@ private fun SetOperatorsButton(enabled: Boolean, close: () -> Unit) { ) } +private fun acceptConditions(selectedOperatorIds: Set) { + withBGApi { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + val r = chatController.acceptConditions(chatModel.remoteHostId(), conditionsId = conditionsId, operatorIds = selectedOperatorIds.toList()) + if (r != null) { + chatModel.conditions.value = r + val enabledOps = enabledOperators(r.serverOperators, selectedOperatorIds) + if (enabledOps != null) { + val r2 = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOps) + if (r2 != null) { + chatModel.conditions.value = r2 + completeOnboarding() + } + } else { + completeOnboarding() + } + } + } +} + @Composable private fun AcceptConditionsButton( enabled: Boolean, selectedOperatorIds: State> ) { - fun continueOnAccept() { - if (appPlatform.isDesktop) { - continueToNextStep() - } else { - continueToSetNotificationsAfterAccept() - } - } OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.onboarding_conditions_accept, onboarding = null, enabled = enabled, - onclick = { - withBGApi { - val conditionsId = chatModel.conditions.value.currentConditions.conditionsId - val r = chatController.acceptConditions(chatModel.remoteHostId(), conditionsId = conditionsId, operatorIds = selectedOperatorIds.value.toList()) - if (r != null) { - chatModel.conditions.value = r - val enabledOperators = enabledOperators(r.serverOperators, selectedOperatorIds.value) - if (enabledOperators != null) { - val r2 = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators) - if (r2 != null) { - chatModel.conditions.value = r2 - continueOnAccept() - } - } else { - continueOnAccept() - } - } - } - } + onclick = { acceptConditions(selectedOperatorIds.value) } ) } -private fun continueToNextStep() { - appPrefs.onboardingStage.set(if (appPlatform.isAndroid) OnboardingStage.Step4_SetNotificationsMode else OnboardingStage.OnboardingComplete) -} - -private fun continueToSetNotificationsAfterAccept() { - appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) - ModalManager.fullscreen.showModalCloseable(showClose = false) { SetNotificationsMode(chatModel) } +private fun completeOnboarding() { + appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) } private fun enabledOperators(operators: List, selectedOperatorIds: Set): List? { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index aff02e90f5..703d295523 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -1,8 +1,7 @@ package chat.simplex.common.views.onboarding import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -15,7 +14,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* @@ -24,21 +23,26 @@ import dev.icerock.moko.resources.StringResource @Composable fun HowItWorks(user: User?, onboardingStage: SharedPreference? = null) { - ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) - ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues) - ReadableText(MR.strings.only_client_devices_store_contacts_groups_e2e_encrypted_messages) - ReadableText(MR.strings.all_message_and_files_e2e_encrypted) - if (onboardingStage == null) { - ReadableTextWithLink(MR.strings.read_more_in_github_with_link, "https://github.com/simplex-chat/simplex-chat#readme") + Column(Modifier.fillMaxSize().padding(horizontal = if (appPlatform.isDesktop) DEFAULT_PADDING * 2 else DEFAULT_PADDING)) { + Spacer(Modifier.statusBarsPadding().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) + val paraPadding = PaddingValues(bottom = if (appPlatform.isDesktop) 10.dp else 12.dp) + Column(Modifier.weight(1f).padding(bottom = DEFAULT_PADDING).verticalScroll(rememberScrollState())) { + Text(stringResource(MR.strings.why_built_heading), style = MaterialTheme.typography.h1, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) + ReadableText(MR.strings.why_built_p1, padding = paraPadding) + ReadableText(MR.strings.why_built_p2, padding = paraPadding) + ReadableText(MR.strings.why_built_p3, padding = paraPadding) + ReadableText(MR.strings.why_built_p4, padding = paraPadding) + ReadableText(MR.strings.why_built_p5, padding = paraPadding) + ReadableText(MR.strings.why_built_p6, padding = paraPadding) + ReadableText(MR.strings.why_built_p7, padding = paraPadding) + ReadableText(MR.strings.why_built_tagline, padding = paraPadding) } - - Spacer(Modifier.fillMaxHeight().weight(1f)) - if (onboardingStage != null) { - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Column( + Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), + horizontalAlignment = Alignment.CenterHorizontally + ) { OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.fullscreen.closeModal() }) - // Reserve space TextButtonBelowOnboardingButton("", null) } } @@ -67,7 +71,7 @@ fun ReadableTextWithLink(stringResId: StringResource, link: String, textAlign: T newStyles } val uriHandler = LocalUriHandler.current - Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { if (simplexLink) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openUriCatching(link) }, textAlign = textAlign, lineHeight = 22.sp) + Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { if (simplexLink) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openExternalLink(link) }, textAlign = textAlign, lineHeight = 22.sp) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt index 9e48f4b2bd..e902b7947e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt @@ -3,6 +3,7 @@ package chat.simplex.common.views.onboarding import SectionTextFooter import SectionView import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -57,7 +58,7 @@ private fun LinkAMobileLayout( ModalView({ appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }) { Column(Modifier.fillMaxSize().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) { Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) + AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), overrideTitleColor = MaterialTheme.colors.onBackground) } Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) { Column( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt new file mode 100644 index 0000000000..684bfb0053 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt @@ -0,0 +1,161 @@ +package chat.simplex.common.views.onboarding + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.* +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.isInDarkTheme +import chat.simplex.common.views.helpers.ModalManager +import chat.simplex.common.views.helpers.mixWith +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops +import chat.simplex.res.MR +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.compose.painterResource + +/** + * A layout for onboarding screens: image + content + spacer + button. + * The spacer shrinks first (down to [minSpacerHeight]), then the image shrinks. + * Button is always at the bottom. + */ +@Composable +fun OnboardingShrinkingLayout( + modifier: Modifier = Modifier, + topPadding: Dp = 0.dp, + minSpacerHeight: Dp = 20.dp, + image: @Composable () -> Unit, + content: @Composable () -> Unit, + button: @Composable () -> Unit +) { + Layout( + contents = listOf(image, content, button), + modifier = modifier + ) { (imageMeasurables, contentMeasurables, buttonMeasurables), constraints -> + val width = constraints.maxWidth + val height = constraints.maxHeight + val childConstraints = constraints.copy(minWidth = 0, minHeight = 0) + + // 1. Measure fixed content (texts) and button first + val contentPlaceable = contentMeasurables.first().measure(childConstraints) + val buttonPlaceable = buttonMeasurables.first().measure(childConstraints) + val minSpacer = minSpacerHeight.roundToPx() + + // 2. Image gets remaining after top padding + content + button + minimum spacer + val topPad = topPadding.roundToPx() + val reservedHeight = topPad + contentPlaceable.height + buttonPlaceable.height + minSpacer + val imageMaxHeight = (height - reservedHeight).coerceAtLeast(0) + val imagePlaceable = imageMeasurables.first().measure( + childConstraints.copy(maxWidth = width, maxHeight = imageMaxHeight) + ) + + // 3. Spacer fills whatever is left between content and button + val usedHeight = topPad + imagePlaceable.height + contentPlaceable.height + buttonPlaceable.height + val spacerHeight = (height - usedHeight).coerceAtLeast(minSpacer) + + // 4. Place: image centered horizontally, rest below + layout(width, height) { + var y = topPad + imagePlaceable.placeRelative((width - imagePlaceable.width) / 2, y) + y += imagePlaceable.height + contentPlaceable.placeRelative((width - contentPlaceable.width) / 2, y) + y += contentPlaceable.height + y += spacerHeight + buttonPlaceable.placeRelative((width - buttonPlaceable.width) / 2, y) + } + } +} + +@Composable +fun OnboardingImage( + lightImage: ImageResource, + darkImage: ImageResource, + fallbackIcon: ImageResource, + modifier: Modifier = Modifier, + aspectRatio: Float = 1f +) { + if (BuildConfigCommon.SIMPLEX_ASSETS) { + Image( + painterResource(if (isInDarkTheme()) darkImage else lightImage), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxWidth().then(modifier) + ) + } else { + val isDark = isInDarkTheme() + val stops = if (isDark) darkStops else lightStops + val scale = if (isDark) 1.5f else 1.2f + Box( + modifier + .aspectRatio(aspectRatio) + .clip(RoundedCornerShape(24.dp)) + .drawBehind { + val gp = gradientPoints(size.height / size.width, scale) + drawRect( + Brush.linearGradient( + colorStops = stops, + start = Offset(gp.startX * size.width, gp.startY * size.height), + end = Offset(gp.endX * size.width, gp.endY * size.height) + ) + ) + }, + contentAlignment = Alignment.Center + ) { + Icon( + painterResource(fallbackIcon), + contentDescription = null, + modifier = Modifier.size(80.dp), + tint = MaterialTheme.colors.primary + ) + } + } +} + +@Composable +fun DesktopOnboardingShell(stage: OnboardingStage, content: @Composable () -> Unit) { + Row(Modifier.fillMaxSize()) { + Box( + Modifier.weight(0.382f).fillMaxHeight() + .background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.985f)) + .padding(horizontal = DEFAULT_PADDING), + contentAlignment = Alignment.Center + ) { + when (stage) { + OnboardingStage.Step1_SimpleXInfo -> + OnboardingImage(MR.images.intro, MR.images.intro_light, MR.images.ic_forum, Modifier.fillMaxWidth()) + OnboardingStage.Step2_CreateProfile, + OnboardingStage.Step2_5_SetupDatabasePassphrase, + OnboardingStage.LinkAMobile -> + OnboardingImage(MR.images.your_profile, MR.images.your_profile_light, MR.images.ic_person, Modifier.fillMaxWidth()) + OnboardingStage.Step3_ChooseServerOperators, + OnboardingStage.Step3_CreateSimpleXAddress, + OnboardingStage.Step4_SetNotificationsMode -> + OnboardingImage(MR.images.your_network, MR.images.your_network_light, MR.images.ic_dns, Modifier.fillMaxWidth()) + OnboardingStage.Step4_NetworkCommitments -> + OnboardingImage(MR.images.network_commitments, MR.images.network_commitments_light, MR.images.ic_shield, Modifier.fillMaxWidth(), aspectRatio = 1.5f) + else -> {} + } + } + Divider(Modifier.fillMaxHeight().width(1.dp)) + Box(Modifier.weight(0.618f).fillMaxHeight().clipToBounds()) { + content() + ModalManager.fullscreen.showInView() + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt index 510df13c3d..7af364b855 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt @@ -6,7 +6,8 @@ enum class OnboardingStage { LinkAMobile, Step2_5_SetupDatabasePassphrase, Step3_ChooseServerOperators, - Step3_CreateSimpleXAddress, - Step4_SetNotificationsMode, + Step3_CreateSimpleXAddress, // deprecated + Step4_SetNotificationsMode, // deprecated + Step4_NetworkCommitments, OnboardingComplete } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index 84f473067f..adcfb8b194 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -5,8 +5,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.stringResource @@ -14,27 +14,21 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.model.ChatModel import chat.simplex.common.model.NotificationsMode import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.changeNotificationsMode import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource @Composable -fun SetNotificationsMode(m: ChatModel) { - LaunchedEffect(Unit) { - prepareChatBeforeFinishingOnboarding() - } - +fun SetNotificationsMode(currentMode: MutableState, onDone: () -> Unit) { CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { ColumnWithScrollBar(Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { Box(Modifier.align(Alignment.CenterHorizontally)) { AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title), bottomPadding = DEFAULT_PADDING) } - val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } Column(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingInformationButton( stringResource(MR.strings.onboarding_notifications_mode_subtitle), @@ -43,34 +37,28 @@ fun SetNotificationsMode(m: ChatModel) { } Spacer(Modifier.weight(1f)) Column(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING)) { - SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc_short)) { + SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc_short), icon = painterResource(MR.images.ic_bolt)) { currentMode.value = NotificationsMode.SERVICE } - SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc_short)) { + SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc_short), icon = painterResource(MR.images.ic_timer)) { currentMode.value = NotificationsMode.PERIODIC } - SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc_short)) { + SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc_short), icon = painterResource(MR.images.ic_bolt_off)) { currentMode.value = NotificationsMode.OFF } } Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier, - labelId = MR.strings.use_chat, - onboarding = OnboardingStage.OnboardingComplete, - onclick = { - changeNotificationsMode(currentMode.value, m) - ModalManager.fullscreen.closeModals() - } + labelId = MR.strings.ok, + onboarding = null, + onclick = onDone ) - // Reserve space - TextButtonBelowOnboardingButton("", null) } } } } - SetNotificationsModeAdditions() } @Composable @@ -78,20 +66,31 @@ expect fun SetNotificationsModeAdditions() @Composable fun SelectableCard(currentValue: State, newValue: T, title: String, description: AnnotatedString, onSelected: (T) -> Unit) { + SelectableCard(currentValue, newValue, title, description, icon = null, onSelected) +} + +@Composable +fun SelectableCard(currentValue: State, newValue: T, title: String, description: AnnotatedString, icon: Painter?, onSelected: (T) -> Unit) { + val titleColor = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary TextButton( onClick = { onSelected(newValue) }, border = BorderStroke(1.dp, color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), shape = RoundedCornerShape(35.dp), ) { Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp).fillMaxWidth()) { - Text( - title, - style = MaterialTheme.typography.h3, - fontWeight = FontWeight.Medium, - color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally), - textAlign = TextAlign.Center - ) + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally)) { + if (icon != null) { + Icon(icon, null, Modifier.size(18.dp), tint = titleColor) + Spacer(Modifier.width(8.dp)) + } + Text( + title, + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = titleColor, + textAlign = TextAlign.Center + ) + } Text(description, Modifier.align(Alignment.CenterHorizontally), fontSize = 15.sp, @@ -105,7 +104,7 @@ fun SelectableCard(currentValue: State, newValue: T, title: String, descr } @Composable -private fun NotificationBatteryUsageInfo() { +fun NotificationBatteryUsageInfo() { ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_battery), withPadding = false) Text(stringResource(MR.strings.onboarding_notifications_mode_service), style = MaterialTheme.typography.h3, color = MaterialTheme.colors.secondary) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index c6eceb0ce2..9ef72a7f12 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -107,7 +107,7 @@ private fun SetupDatabasePassphraseLayout( Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally, ) { - AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) + AppBarTitle(stringResource(MR.strings.setup_database_passphrase), overrideTitleColor = MaterialTheme.colors.onBackground) val onClickUpdate = { // Don't do things concurrently. Shouldn't be here concurrently, just in case diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index e5d00fddd1..74dadcd671 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -11,6 +11,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale @@ -21,11 +24,15 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.migration.MigrateToDeviceView import chat.simplex.common.views.migration.MigrationToState import chat.simplex.res.MR @@ -36,12 +43,16 @@ import kotlin.math.floor @Composable fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { if (onboarding) { - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false, showAppBar = false) { - SimpleXInfoLayout( - user = chatModel.currentUser.value, - onboardingStage = chatModel.controller.appPrefs.onboardingStage - ) + if (appPlatform.isDesktop) { + SimpleXInfoDesktop(chatModel) + } else { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, showAppBar = false) { + SimpleXInfoLayout( + user = chatModel.currentUser.value, + onboardingStage = chatModel.controller.appPrefs.onboardingStage + ) + } } } } else { @@ -52,40 +63,106 @@ fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { } } +@Composable +private fun SimpleXInfoDesktop(chatModel: ChatModel) { + val user = chatModel.currentUser.value + val onboardingStage = chatModel.controller.appPrefs.onboardingStage + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(Modifier.height(DEFAULT_PADDING)) + Box(Modifier.widthIn(max = 600.dp).fillMaxWidth(0.45f).align(Alignment.CenterHorizontally)) { + SimpleXLogo() + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 600.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_be_free), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Text(stringResource(MR.strings.onboarding_private_and_secure), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + ReadableText(MR.strings.onboarding_first_network, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.secondary)) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton(user, onboardingStage) + TextButtonBelowOnboardingButton(stringResource(MR.strings.why_simplex_is_built), icon = painterResource(MR.images.ic_info), onClick = { + ModalManager.fullscreen.showModal(forceAnimated = true) { HowItWorks(user, onboardingStage) } + }) + } + } + } + } + LaunchedEffect(Unit) { + if (chatModel.migrationState.value != null && !ModalManager.fullscreen.hasModalsOpen()) { + ModalManager.fullscreen.showCustomModal(animated = false) { close -> MigrateToDeviceView(close) } + } + } +} + @Composable fun SimpleXInfoLayout( user: User?, onboardingStage: SharedPreference? ) { - ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { - Box(Modifier.widthIn(max = if (appPlatform.isAndroid) 250.dp else 500.dp).padding(top = DEFAULT_PADDING + 8.dp), contentAlignment = Alignment.Center) { + val topBar = onboardingStage == null && !appPrefs.oneHandUI.state.value + val modifier = Modifier.fillMaxSize().systemBarsPadding().padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING) + Column(if (topBar) modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier) else modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.padding(top = DEFAULT_PADDING * 2).widthIn(max = if (appPlatform.isAndroid) 185.dp else 160.dp), contentAlignment = Alignment.Center) { SimpleXLogo() } - - OnboardingInformationButton( - stringResource(MR.strings.next_generation_of_private_messaging), - onClick = { ModalManager.fullscreen.showModal { HowItWorks(user, onboardingStage) } }, - ) - - Spacer(Modifier.weight(1f)) - - Column { - InfoRow(painterResource(MR.images.privacy), MR.strings.privacy_redefined, MR.strings.first_platform_without_user_ids, width = 60.dp) - InfoRow(painterResource(MR.images.shield), MR.strings.immune_to_spam_and_abuse, MR.strings.people_can_connect_only_via_links_you_share, width = 46.dp) - InfoRow(painterResource(if (isInDarkTheme()) MR.images.decentralized_light else MR.images.decentralized), MR.strings.decentralized, MR.strings.opensource_protocol_and_code_anybody_can_run_servers) - } - - Column(Modifier.fillMaxHeight().weight(1f)) { } - - if (onboardingStage != null) { - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally,) { - OnboardingActionButton(user, onboardingStage) - TextButtonBelowOnboardingButton(stringResource(MR.strings.migrate_from_another_device)) { - chatModel.migrationState.value = MigrationToState.PasteOrScanLink - ModalManager.fullscreen.showCustomModal { close -> MigrateToDeviceView(close) } + OnboardingShrinkingLayout( + modifier = Modifier.fillMaxSize(), + image = { + Column(Modifier.padding(vertical = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingImage( + MR.images.intro, MR.images.intro_light, MR.images.ic_forum, + modifier = if (appPlatform.isAndroid) Modifier.fillMaxWidth() else Modifier.heightIn(max = 280.dp) + ) + } + }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + stringResource(MR.strings.onboarding_be_free), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 42.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Text( + stringResource(MR.strings.onboarding_private_and_secure), + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.secondary, + fontWeight = FontWeight.Medium, + lineHeight = 25.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 14.dp) + ) + Text( + stringResource(MR.strings.onboarding_first_network), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + } + }, + button = { + if (onboardingStage != null) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton(user, onboardingStage) + TextButtonBelowOnboardingButton(stringResource(MR.strings.why_simplex_is_built), icon = painterResource(MR.images.ic_info), onClick = { + ModalManager.fullscreen.showModal { HowItWorks(user, onboardingStage) } + }) } + } else { + Spacer(Modifier) } } + ) } LaunchedEffect(Unit) { if (chatModel.migrationState.value != null && !ModalManager.fullscreen.hasModalsOpen()) { @@ -101,25 +178,11 @@ fun SimpleXLogo() { contentDescription = stringResource(MR.strings.image_descr_simplex_logo), contentScale = ContentScale.FillWidth, modifier = Modifier - .padding(vertical = DEFAULT_PADDING) + .padding(bottom = 10.dp) .fillMaxWidth() ) } -@Composable -private fun InfoRow(icon: Painter, titleId: StringResource, textId: StringResource, width: Dp = 58.dp) { - Row(Modifier.padding(bottom = 27.dp), verticalAlignment = Alignment.Top) { - Spacer(Modifier.width((4.dp + 58.dp - width) / 2)) - Image(icon, contentDescription = null, modifier = Modifier - .width(width)) - Spacer(Modifier.width((4.dp + 58.dp - width) / 2 + DEFAULT_PADDING_HALF + 7.dp)) - Column(Modifier.padding(top = 4.dp), verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF)) { - Text(stringResource(titleId), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.h3, lineHeight = 24.sp) - Text(stringResource(textId), lineHeight = 24.sp, style = MaterialTheme.typography.body1, color = MaterialTheme.colors.secondary) - } - } -} - @Composable expect fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference, onclick: (() -> Unit)? = null) @@ -155,16 +218,20 @@ fun OnboardingActionButton( } @Composable -fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) { +fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?, icon: Painter? = null) { val state = getKeyboardState() val enabled = onClick != null val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else 7.5.dp) val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else 7.5.dp) if ((appPlatform.isAndroid && state.value == KeyboardState.Closed) || topPadding > 0.dp) { TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = enabled) { + if (icon != null) { + Icon(icon, null, tint = MaterialTheme.colors.primary) + Spacer(Modifier.width(4.dp)) + } Text( text, - Modifier.padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF, bottom = 5.dp), + Modifier.padding(vertical = 5.dp), color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, fontWeight = FontWeight.Medium, textAlign = TextAlign.Center @@ -219,6 +286,7 @@ fun OnboardingInformationButton( textLayoutResult = it }, style = MaterialTheme.typography.button, + fontWeight = FontWeight.Medium, color = MaterialTheme.colors.primary ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 89b2f97ee7..e1415d071d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -59,7 +59,7 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool Icon( painterResource(MR.images.ic_open_in_new), stringResource(titleId), tint = MaterialTheme.colors.primary, modifier = Modifier - .clickable { if (link.startsWith("simplex:")) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openUriCatching(link) } + .clickable { if (link.startsWith("simplex:")) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openExternalLink(link) } ) } @@ -229,7 +229,7 @@ fun ReadMoreButton(url: String) { interactionSource = remember { MutableInteractionSource() }, indication = null ) { - uriHandler.openUriCatching(url) + uriHandler.openExternalLink(url) } ) Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.whats_new_read_more), tint = MaterialTheme.colors.primary) @@ -882,7 +882,7 @@ private val versionDescriptions: List = listOf( ), VersionDescription( version = "v6.5", - post = "https://simplex.chat/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html", + post = "https://simplex.chat/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html", features = listOf( VersionFeature.FeatureDescription( icon = null, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt new file mode 100644 index 0000000000..b20cfe3096 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt @@ -0,0 +1,226 @@ +package chat.simplex.common.views.onboarding + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops +import chat.simplex.common.views.usersettings.changeNotificationsMode +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +internal object OnboardingSharedState { + var selectedOperatorIds: Set = emptySet() +} + +@Composable +fun YourNetworkView(chatModel: ChatModel) { + LaunchedEffect(Unit) { + prepareChatBeforeFinishingOnboarding() + } + + val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } + val selectedOperatorIds = remember { + mutableStateOf(serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet()) + } + + LaunchedEffect(selectedOperatorIds.value) { + OnboardingSharedState.selectedOperatorIds = selectedOperatorIds.value + } + + val notificationMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } + + if (appPlatform.isDesktop) { + YourNetworkDesktop(serverOperators, selectedOperatorIds) + } else { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, showAppBar = false) { + OnboardingShrinkingLayout( + modifier = Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer) + .systemBarsPadding() + .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), + topPadding = DEFAULT_PADDING, + image = { + Column(Modifier.padding(vertical = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingImage( + MR.images.your_network, MR.images.your_network_light, MR.images.ic_dns, + modifier = Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() + ) + } + }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + stringResource(MR.strings.onboarding_your_network), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 42.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Text( + stringResource(MR.strings.onboarding_network_routers_cannot_know), + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.secondary, + lineHeight = 25.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 14.dp) + ) + Column( + Modifier.padding(top = DEFAULT_PADDING_HALF), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + ConfigureRoutersButton(serverOperators, selectedOperatorIds) { + ModalManager.fullscreen.showCustomModal { close -> + ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + } + ConfigureNotificationsButton(notificationMode) { + ModalManager.fullscreen.showModalCloseable { close -> + SetNotificationsMode(notificationMode, close) + } + } + } + } + }, + button = { + Column( + Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OnboardingActionButton( + modifier = Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth(), + labelId = MR.strings.onboarding_network_operators_continue, + onboarding = null, + onclick = { + changeNotificationsMode(notificationMode.value, chatModel) + appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments) + } + ) + } + } + ) + } + } + } +} + +@Composable +private fun YourNetworkDesktop( + serverOperators: State>, + selectedOperatorIds: MutableState> +) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_your_network), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Text(stringResource(MR.strings.onboarding_network_routers_cannot_know), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING)) + ConfigureRoutersButton(serverOperators, selectedOperatorIds) { + ModalManager.fullscreen.showCustomModal(forceAnimated = true) { close -> + ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + } + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + Modifier.widthIn(min = 300.dp), + labelId = MR.strings.onboarding_network_operators_continue, + onboarding = null, + onclick = { + appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments) + } + ) + TextButtonBelowOnboardingButton("", null) + } + } + } + } +} + +@Composable +private fun ConfigureRoutersButton(serverOperators: State>, selectedOperatorIds: State>, onClick: () -> Unit) { + Box( + modifier = Modifier + .clip(CircleShape) + .clickable { onClick() } + ) { + Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) { + Text( + stringResource(MR.strings.onboarding_configure_routers), + style = MaterialTheme.typography.button, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary + ) + serverOperators.value.forEach { op -> + Image( + painterResource(op.logo), + contentDescription = null, + modifier = Modifier.size(22.dp), + colorFilter = if (selectedOperatorIds.value.contains(op.operatorId)) null else ColorFilter.colorMatrix(ColorMatrix().apply { + setToSaturation(0f) + }) + ) + } + } + } +} + +@Composable +private fun ConfigureNotificationsButton(notificationMode: State, onClick: () -> Unit) { + val icon = when (notificationMode.value) { + NotificationsMode.SERVICE -> MR.images.ic_bolt + NotificationsMode.PERIODIC -> MR.images.ic_timer + NotificationsMode.OFF -> MR.images.ic_bolt_off + } + Box( + modifier = Modifier + .clip(CircleShape) + .clickable { onClick() } + ) { + Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) { + Text( + stringResource(MR.strings.onboarding_configure_notifications), + style = MaterialTheme.typography.button, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary + ) + Icon( + painterResource(icon), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt index 761a74d6e4..0c31b062dd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt @@ -198,7 +198,7 @@ private fun howToButton() { val uriHandler = LocalUriHandler.current Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.clickable { uriHandler.openUriCatching("https://simplex.chat/docs/webrtc.html#configure-mobile-apps") } + modifier = Modifier.clickable { uriHandler.openExternalLink("https://simplex.chat/docs/webrtc.html#configure-mobile-apps") } ) { Text(stringResource(MR.strings.how_to), color = MaterialTheme.colors.primary) Icon( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 7ea656e1e4..c826b1dc51 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -75,7 +75,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: ( } val simplexTeamUri = - "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" + "simplex:/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw?h=smp6.simplex.im" @Composable fun SettingsLayout( @@ -207,7 +207,7 @@ fun ChatLockItem( } @Composable private fun ContributeItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) { + SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat#contribute") }) { Icon( painterResource(MR.images.ic_keyboard), contentDescription = "GitHub", @@ -235,7 +235,7 @@ fun ChatLockItem( } @Composable private fun StarOnGithubItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat") }) { Icon( painter = painterResource(MR.images.ic_github), contentDescription = "GitHub", @@ -268,7 +268,7 @@ fun ChatLockItem( } @Composable fun InstallTerminalAppItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat") }) { Icon( painter = painterResource(MR.images.ic_github), contentDescription = "GitHub", diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index bbd2a0af49..a62a58cb10 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -769,7 +769,7 @@ fun UsageConditionsView( .clip(shape = CircleShape) .clickable { val commitUrl = "https://github.com/simplex-chat/simplex-chat/commit/$commit" - uriHandler.openUriCatching(commitUrl) + uriHandler.openExternalLink(commitUrl) } .padding(horizontal = 6.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, @@ -826,13 +826,22 @@ fun UsageConditionsView( @Composable fun SimpleConditionsView( - rhId: Long? + rhId: Long?, + onAccept: () -> Unit ) { ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false, bottomPadding = DEFAULT_PADDING) Column(modifier = Modifier.weight(1f).padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF)) { ConditionsTextView(rhId) } + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.onboarding_conditions_accept, + onboarding = null, + onclick = onAccept + ) + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 1449e0cd0d..9e11b9a932 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -500,7 +500,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) { Text(d) } val website = serverOperator.info.website - Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(website) }) + Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(website) }) } } } @@ -511,7 +511,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) { SectionView { SectionItemView { val (text, link) = selfhost - Text(text, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(link) }) + Text(text, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(link) }) } } } @@ -787,7 +787,7 @@ private fun ConditionsLinkView(conditionsLink: String) { SectionItemView { val uriHandler = LocalUriHandler.current Text(stringResource(MR.strings.operator_conditions_failed_to_load), color = MaterialTheme.colors.onBackground) - Text(conditionsLink, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(conditionsLink) }) + Text(conditionsLink, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(conditionsLink) }) } } @@ -821,13 +821,13 @@ fun ConditionsLinkButton() { val commit = chatModel.conditions.value.currentConditions.conditionsCommit ItemAction(stringResource(MR.strings.operator_open_conditions), painterResource(MR.images.ic_draft), onClick = { val mdUrl = "https://github.com/simplex-chat/simplex-chat/blob/$commit/PRIVACY.md" - uriHandler.openUriCatching(mdUrl) showMenu.value = false + uriHandler.openExternalLink(mdUrl) }) ItemAction(stringResource(MR.strings.operator_open_changes), painterResource(MR.images.ic_more_horiz), onClick = { val commitUrl = "https://github.com/simplex-chat/simplex-chat/commit/$commit" - uriHandler.openUriCatching(commitUrl) showMenu.value = false + uriHandler.openExternalLink(commitUrl) }) } IconButton({ showMenu.value = true }) { @@ -838,11 +838,7 @@ fun ConditionsLinkButton() { private fun internalUriHandler(parentUriHandler: UriHandler): UriHandler = object: UriHandler { override fun openUri(uri: String) { - if (uri.startsWith("https://simplex.chat/contact#")) { - openVerifiedSimplexUri(uri) - } else { - parentUriHandler.openUriCatching(uri) - } + parentUriHandler.openExternalLink(uri) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt index b232c7994e..3be2456b72 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt @@ -335,7 +335,7 @@ private fun HowToButton() { SettingsActionItem( painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.how_to_use_your_servers), - { uriHandler.openUriCatching("https://simplex.chat/docs/server.html") }, + { uriHandler.openExternalLink("https://simplex.chat/docs/server.html") }, textColor = MaterialTheme.colors.primary, iconColor = MaterialTheme.colors.primary ) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 3045f3035d..95ec53287a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -26,7 +26,7 @@ خوادم الاتصالات الجديدة لملف تعريف الدردشة الحالي الخاص بك سيتم تغيير عنوان الاستلام إلى خادم مختلف. سيتم إكمال تغيير العنوان بعد اتصال المرسل بالإنترنت. هذا الرابط ليس رابط اتصال صالح! - يسمح + اسمح أضِف خوادم مُعدة مسبقًا أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! @@ -106,7 +106,7 @@ تجزئة رسالة سيئة معرّف رسالة سيئ انتهت المكالمة - تغير + غيِّر لون إضافي ثانوي " \nمتوفر في v5.1" @@ -122,7 +122,7 @@ 1 دقيقة 30 ثانية ألغِ الرسالة الحيّة - إلغاء + ألغِ لكل جهة اتصال وعضو في المجموعة\n. الرجاء ملاحظة: إذا كان لديك العديد من الاتصالات، فقد يكون استهلاك البطارية وحركة المرور أعلى بكثير وقد تفشل بعض الاتصالات.]]> جارٍ الاتصال… مكالمة صوتية @@ -162,19 +162,19 @@ مفعّل مفعّلة لك يمكن لجهات الاتصال تحديد الرسائل لحذفها؛ ستتمكن من مشاهدتها. - جار الاتصال… + يتصل… خطأ في الإتصال (المصادقة) خطأ في حذف جهة الاتصال جهة الاتصال مخفية: - نسخ + انسخ اتصل متصل انضمام إلى المجموعة؟ اتصل عبر رابط لمرة واحدة؟ تغيير عنوان الاستلام؟ نٌسخت إلى الحافظة - مسح - امسح الدردشة + امحُ + امحُ الدردشة أنشئ عنوان الدردشات تأكيد عبارة المرور الجديدة… @@ -199,19 +199,19 @@ تأكد من بيانات اعتمادك أنشئ عنوان SimpleX متابعة - تحدث مع المطورين + دردش مع المطوِّرين سياق الأيقونة إحباط تغيير العنوان؟ إحباط سيتم إحباط تغيير العنوان. سيتم استخدام عنوان الاستلام القديم. - مسح الدردشة؟ + محو الدردشة؟ وحدة تحكم الدردشة ضبط خوادم ICE الاتصال ملف تعريف الدردشة الإصدار الأساسي: v%s أنشئ ملف تعريف - جار الاتصال… + يتصل… انتهى متصل %1$d تخطت الرسائل @@ -221,13 +221,13 @@ خطأ في تعمية قاعدة البيانات توقفت الدردشة مكتمل - جاري الاتصال (أعلن) + يتصل (أُعلن) الاتصال إحباط تغيير العنوان أنشئ مجموعة سرية قارن رموز الأمان مع جهات اتصالك. الواجهة الصينية والاسبانية - مسح + امحُ %1$s يريد التواصل معك عبر جارِ تغيير العنوان… جارِ تغيير العنوان ل%s… @@ -240,17 +240,17 @@ تغيير وضع التدمير الذاتي تغيير رمز المرور التدمير الذاتي تأكيد ترقيات قاعدة البيانات - الاتصال (دعوة مقدمة) - مسح + يتصل (دعوة مقدمة) + امحُ خطأ في إنشاء رابط المجموعة (حاضِر) فعّل أبقِ TCP على قيد الحياة - جار الاتصال… - جار الاتصال… + يتصل… + يتصل… أرسلت طلب الاتصال! حُذفت قاعدة بيانات الدردشة جارِ تغيير العنوان… - جار الاتصال (قُبِل) + يتصل (قُبِل) فُحصت جهة الاتصال %1$s أعضاء أنشئ رابط المجموعة @@ -271,7 +271,7 @@ اتصل عبر عنوان التواصل؟ خطأ في حذف رابط المجموعة التحقق من الرسائل الجديدة كل 10 دقائق لمدة تصل إلى دقيقة واحدة - جار الاتصال + يتصل خطأ خطأ في حذف ملف تعريف المستخدم زر الاغلاق @@ -279,7 +279,7 @@ تغيير الدور أدخل كلمة المرور في البحث تسمح جهة الاتصال - تأكيد + أكِّد جهة الاتصال ليست متصلة بعد! اتصال تواصل عبر الرابط @@ -294,7 +294,7 @@ خطأ في حذف اتصال جهة الاتصال المنتظر أدخل رسالة ترحيب… متصل - جار الاتصال + يتصل مفعّلة للاتصال تغيير رمز المرور متصل @@ -308,18 +308,18 @@ تواصل عبر الرابط / رمز QR أنشئ رابط دعوة لمرة واحدة تحقق من عنوان الخادم وحاول مرة أخرى. - امسح التحقُّق + امحُ التحقُّق أنشئ عنوانًا للسماح للأشخاص بالتواصل معك. أدخل الخادم يدويًا ملون لدى جهة الاتصال التعمية بين الطريفين أنشئ أنشئ ملف تعريفك - مكالمة جارية... + مكالمة جارية فعّل التدمير الذاتي الموافقة على التعمية… الموافقة على التعمية لـ%s… - متصل (مقدم) + يتصل (مقدم) وافق التعمية التعمية نعم التعمية نعم ل%s @@ -335,7 +335,7 @@ احذف جميع الملفات احذف بعد احذف الملف - حذف + احذف حذف رسالة العضو؟ احذف احذف الرسائل @@ -398,7 +398,7 @@ مخصص تخصيص ومشاركة سمات الألوان. الخروج بدون حفظ - أدوات المطور + أدوات المطوِّر احذف قائمة الانتظار خطأ في تحديث خصوصية المستخدم مسح رمز QR.]]> @@ -471,9 +471,8 @@ يمكن للأعضاء إرسال الملفات والوسائط. تفضيلات المجموعة سريع ولا تنتظر حتى يصبح المرسل متصلاً بالإنترنت! - إخفاء + أخفِ كيفية الاستخدام - كيف يعمل SimpleX التخفي عبر رابط عنوان جهة الاتصال رمز الأمان غير صحيحة! الإشعارات الفورية مُعطَّلة @@ -602,7 +601,7 @@ مصادقة الجهاز غير مفعّلة. يمكنك تشغيل قفل SimpleX عبر الإعدادات، بمجرد تفعيل مصادقة الجهاز. نزّل الملف عطّل قفل SimpleX - تحرير + حرّر اسم ملف التعريف: البريد الإلكتروني أدخل أسمك: @@ -709,7 +708,7 @@ خطأ في تسليم الرسالة الشبكة والخوادم إشراف - فتح في تطبيق الجوال، ثم انقر فوق اتصال في التطبيق.]]> + فتح في تطبيق الجوال، ثم انقر فوق اتصال في التطبيق.]]> تحت الإشراف في: %s ردود الفعل الرسائل ممنوعة في هذه الدردشة. أُشرف بواسطة %s @@ -798,7 +797,7 @@ جارِ فتح قاعدة البيانات… جهة اتصالك فقط يمكنها إرسال رسائل صوتية. ألصق - لم يُعثر على عبارة المرور في Keystore، يُرجى إدخالها يدويًا. ربما حدث هذا إذا استعدت بيانات التطبيق باستخدام أداة النسخ الاحتياطي. إذا لم يكن الأمر كذلك، يُرجى التواصل مع المطورين. + لم يُعثر على عبارة المرور في Keystore، يُرجى إدخالها يدويًا. ربما حدث هذا إذا استعدت بيانات التطبيق باستخدام أداة النسخ الاحتياطي. إذا لم يكن الأمر كذلك، يُرجى التواصل مع المطوِّرين. افتح الدردشة قد يؤدي فتح الرابط في المتصفح إلى تقليل خصوصية الاتصال وأمانه. ستظهر روابط SimpleX غير الموثوقة باللون الأحمر. أنت فقط يمكنك إضافة ردود الفعل على الرسالة. @@ -816,7 +815,7 @@ مكالمة قيد الانتظار تقوم أجهزة العميل فقط بتخزين ملفات تعريف المستخدمين وجهات الاتصال والمجموعات والرسائل. صفّر الألوان - حفظ + احفظ عنوان الخادم المُعد مسبقًا حفظ وإشعار أعضاء المجموعة دوري @@ -827,18 +826,18 @@ صورة ملف التعريف الإشعارات خاصة يُرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. - يُرجى تحديث التطبيق والتواصل مع المطورين. + يُرجى تحديث التطبيق والتواصل مع المطوِّرين. دليل المستخدم.]]> غيّر ملفات تعريف الدردشة اسحب الوصول - كشف + اكشف سيتم إيقاف استلام الملف. رفض قيم التطبيق منفذ احفظ إعدادات عنوان SimpleX إعادة تعريف الخصوصية - الرجاء الإبلاغ للمطورين. + يُرجى إبلاغ المطوِّرين بذلك. الخصوصية والأمان أزل إزالة عبارة المرور من Keystore؟ @@ -853,7 +852,7 @@ استلمت إجابة… مستودع GitHub.]]> رفض - يحمي خادم الترحيل عنوان IP الخاص بك، ولكن يمكنه مراقبة مدة المكالمة. + يحمي خادم المُرحل عنوان IP الخاص بك، ولكن يمكنه مراقبة مُدّة المكالمة. الرجاء إدخال كلمة المرور السابقة بعد استعادة نسخة احتياطية لقاعدة البيانات. لا يمكن التراجع عن هذا الإجراء. استعادة النسخة الاحتياطية لقاعدة البيانات؟ حفظ @@ -924,7 +923,7 @@ صفّر المنفذ %d خادم مُعد مسبقًا - يتم استخدام خادم الترحيل فقط إذا لزم الأمر. يمكن لطرف آخر مراقبة عنوان IP الخاص بك. + يُستخدم خادم المُرحل فقط إذا لزم الأمر. يمكن لطرف آخر مراقبة عنوان IP الخاص بك. حفظ وإشعار جهة الاتصال إعادة التشغيل استلمت في: %s @@ -946,7 +945,7 @@ إرسال رسالة إرسال أرسل رسالة حيّة - فشلت تجربة الخادم! + فشل اختبار الخادم! احفظ عبارة المرور في Keystore أرسل رسالة مباشرة إرسال عبر @@ -960,8 +959,8 @@ رسالة مرسلة عيّن تفضيلات المجموعة عيّنها بدلاً من استيثاق النظام. - مشاركة - إرسال + شارك + أرسل احفظ عبارة المرور وافتح الدردشة حدد جهات الاتصال تعيين يوم واحد @@ -1048,7 +1047,7 @@ يبدأ… شُغّل قفل SimpleX أظهر: - أظهر خيارات المطور + أظهر خيارات المطوِّر simplexmq: v%s (%2s) يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور. يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور. @@ -1093,7 +1092,7 @@ اختبر الخوادم لا معرّفات مُستخدم دعم SIMPLEX CHAT - تبديل + بدِّل العنوان الرئيسي سيتم وضع علامة على الرسالة على أنها تحت الإشراف لجميع الأعضاء. انقر للانضمام @@ -1117,7 +1116,7 @@ لاستلام الإشعارات، يُرجى إدخال عبارة مرور قاعدة البيانات مصادقة النظام يعمل التعمية واتفاقية التعمية الجديدة غير مطلوبة. قد ينتج عن ذلك أخطاء في الاتصال! - لا يمكن فك ترميز الصورة. من فضلك، جرب صورة مختلفة أو تواصل مع المطورين. + لا يمكن فك ترميز الصورة. من فضلك، جرّب صورة مختلفة أو تواصل مع المطوِّرين. سيتم حذف الرسالة لجميع الأعضاء. الصور كثيرة! مقاطع الفيديو كثيرة! @@ -1253,13 +1252,13 @@ إلغاء إخفاء ملف تعريف يجب أن تكون جهة الاتصال متصلة بالإنترنت حتى يكتمل الاتصال. \nيمكنك إلغاء هذا الاتصال وإزالة جهة الاتصال (والمحاولة لاحقًا باستخدام رابط جديد). - فتح في تطبيق الجوال.]]> + فتح في تطبيق الجوّال.]]> استخدم للاتصالات الجديدة استخدم الخادم عنوان خادمك قاعدة بيانات دردشتك أنت مدعو إلى المجموعة. انضم للتواصل مع أعضاء المجموعة. - لقد انضممت إلى هذه المجموعة. جارِ الاتصال بدعوة عضو المجموعة. + لقد انضممت إلى هذه المجموعة. يتصل بدعوة عضو المجموعة. غيّرتَ العنوان ل%s إلغاء إخفاء ملف تعريف الدردشة الرسائل الصوتية ممنوعة في هذه الدردشة. @@ -1284,7 +1283,7 @@ مكالمة فيديو الرسائل الصوتية ممنوعة. فتح القفل - رفع الملف + ارفع الملف لا يمكن التحقق منك؛ الرجاء المحاولة مرة اخرى. رسالة صوتية رسالة صوتية… @@ -1292,14 +1291,14 @@ أنت المراقب! تحتاج إلى السماح لجهة اتصالك بإرسال رسائل صوتية لتتمكن من إرسالها. أرسلت جهة اتصالك ملفًا أكبر من الحجم الأقصى المعتمد حاليًا (%1$s). - الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> + الاتصال بمطوِّري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> خادمك يُخزن ملف تعريفك على جهازك ومشاركته فقط مع جهات اتصالك. لا تستطيع خوادم SimpleX رؤية ملف تعريفك. الفيديو مقفل الفيديو مُشغَّل مع رسالة ترحيب اختيارية. قريباً! - هذه الميزة ليست مدعومة بعد. جرب الإصدار القادم. + هذه الميزة ليست مدعومة بعد. جرّب الإصدار القادم. عطّل (الاحتفاظ بتجاوزات المجموعة) فعِّل لجميع المجموعات إرسال الإيصالات مفعّلة لـ%d مجموعات @@ -1366,7 +1365,7 @@ فعّل وضع التخفي عند الاتصال. أرسل لاتصال طُلب اتصال - جارٍ الاتصال بالفعل! + يتصل بالفعل! مجموعات أفضل و%d أحداث أخرى جارٍ انضمام بالفعل إلى المجموعة! @@ -1388,7 +1387,7 @@ اتصل بنفسك؟ سطح المكتب متصل بسطح المكتب - جار الاتصال بسطح المكتب + الاتصال بسطح المكتب أجهزة سطح المكتب الاسم الصحيح لـ%s؟ حذف %d رسالة؟ @@ -1408,10 +1407,10 @@ مُكتشف عبر الشبكة المحلية قطع اتصال سطح المكتب؟ إصدار تطبيق سطح المكتب %s غير متوافق مع هذا التطبيق. - توسيع + وسِّع هل تريد تكرار طلب الاتصال؟ خطأ في إعادة التفاوض بشأن التعمية - %1$s.]]> + %1$s.]]> خطأ لقد انضممت بالفعل إلى المجموعة عبر هذا الرابط. %s و%s @@ -1429,7 +1428,7 @@ (جديد)]]> فك ربط سطح المكتب؟ خيارات سطح المكتب المرتبطة - لا يمكن فك تشفير الفيديو. من فضلك، جرب مقطع فيديو مختلفًا أو اتصل بالمطورين. + لا يمكن فك ترميز الفيديو. من فضلك، جرّب مقطع فيديو مختلفًا أو اتصل بالمطوِّرين. %s متصل أسطح المكتب المرتبطة مجموعات التخفي @@ -1466,7 +1465,7 @@ تحقق من الرمز على الجوّال أدخل اسم الجهاز هذا… خطأ - لقد شاركت مسار ملف غير صالح. أبلغ عن المشكلة لمطوري التطبيق. + لقد شاركت مسار ملف غير صالح. أبلغ عن المشكلة لمطوِّري التطبيق. اسم غير صالح! ألصق عنوان سطح المكتب %1$s!]]> @@ -1483,13 +1482,13 @@ تحقق من الاتصال أعِد التحميل عشوائي - في انتظار اتصال الجوال: - للسماح لتطبيق الجوال بالاتصال بسطح المكتب، افتح هذا المنفذ في جدار الحماية لديك، إذا فعلته + في انتظار اتصال الجوّال: + للسماح لتطبيق الجوّال بالاتصال بسطح المكتب، افتح هذا المنفذ في جدار الحماية لديك، إذا فعّلته أنشئ ملف تعريف الدردشة عرض التحطم فتح منفذ في جدار الحماية - اقطع اتصال الجوالات - لا يوجد جوال متصل + اقطع اتصال الجوّالات + لا يوجد جوّال متصل خطأ في إظهار المحتوى خطأ في إظهار الرسالة انتهت المكالمة %1$s @@ -1526,30 +1525,26 @@ أظهر الأخطاء الداخلية خطأ فادح خطأ داخلي - يُرجى إبلاغ المطورين بذلك: -\n%s - يُرجى إبلاغ المطورين بذلك: -\n%s -\n -\nيوصى بإعادة تشغيل التطبيق. + يُرجى إبلاغ المطوِّرين بذلك: \n%s + يُرجى إبلاغ المطوِّرين بذلك: \n%s \n \nيوصى بإعادة تشغيل التطبيق. أعد تشغيل الدردشة - %s غير نشط]]> + %s غير نشط]]> أظهر مكالمات API البطيئة غير معروف حدّثت ملف التعريف - %s مفقود]]> - %s لديه إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين]]> - %s في حالة سيئة]]> + %s مفقود]]> + %s لديه إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين]]> + %s في حالة سيئة]]> اسم العرض غير صالح! اسم العرض هذا غير صالح. الرجاء اختيار اسم آخر. توقف الاتصال توقف الاتصال - %s بسبب: %s]]> + %s بسبب: %s]]> قُطع الاتصال بسبب: %s - %s]]> - %s]]> + %s]]> + %s]]> سطح المكتب غير نشط - %s مشغول]]> + %s مشغول]]> انتهت المهلة أثناء الاتصال بسطح المكتب قُطع اتصال سطح المكتب الاتصال بسطح المكتب في حالة سيئة @@ -1558,7 +1553,7 @@ يحتوي سطح المكتب على إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين العضو %1$s وظيفة بطيئة - خيارات المطور + خيارات المطوِّر تغيّر العضو %1$s إلى %2$s أزلت عنوان الاتصال أزلت صورة ملف التعريف @@ -1581,7 +1576,7 @@ حدث خطأ أثناء إنشاء الرسالة حدث خطأ أثناء حذف الملاحظات الخاصة ملاحظات خاصة - مسح الملاحظات الخاصة؟ + محو الملاحظات الخاصة؟ أُنشئ في: %s رسالة محفوظة إلغاء حظر العضو للجميع؟ @@ -1604,7 +1599,7 @@ مكالمة فيديو مكالمة صوتية أنهيّ المكالمة - متصفح الويب الافتراضي مطلوب للمكالمات. يُرجى تضبيط المتصفح الافتراضي في النظام، ومشاركة المزيد من المعلومات مع المطورين. + متصفح الويب الافتراضي مطلوب للمكالمات. يُرجى تضبيط المتصفح الافتراضي في النظام، ومشاركة المزيد من المعلومات مع المطوِّرين. حدث خطأ أثناء فتح المتصفح أرشف وأرفع يمكن للمُدراء حظر عضو للجميع. @@ -1648,7 +1643,7 @@ تحقق من عبارة المرور تأكد من أنك تتذكر عبارة مرور قاعدة البيانات لترحيلها. التحقق من عبارة مرور قاعدة البيانات - خطأ في عرض الإشعار، تواصل بالمطورين. + خطأ في عرض الإشعار، تواصل بالمطوِّرين. مكالمات صورة في صورة استخدم التطبيق أثناء المكالمة. رحّل إلى جهاز آخر عبر رمز QR. @@ -1819,8 +1814,7 @@ معلومات قائمة انتظار الرسائل احمِ عنوان IP الخاص بك من مُرحلات المُراسلة التي اختارتها جهات اتصالك. \nفعّل في إعدادات *الشبكة والخوادم*. سمات دردشة جديدة - حدث خطأ أثناء تهيئة WebView. حدّث نظامك إلى الإصدار الجديد. يُرجى التواصل بالمطورين. -\nError: %s + حدث خطأ أثناء تهيئة WebView. حدّث نظامك إلى الإصدار الجديد. يُرجى التواصل بالمطوِّرين. \nError: %s تحسين تسليم الرسائل مع انخفاض استخدام البطارية. مفتاح خاطئ أو عنوان مجموعة الملف غير معروف - على الأرجح حُذف الملف. @@ -1834,8 +1828,7 @@ حالة الرسالة: %s خطأ في النسخ استُخدم هذا الرابط مع جوّال آخر، يُرجى إنشاء رابط جديد على سطح المكتب. - يُرجى التحقق من اتصال الهاتف المحمول وسطح المكتب بنفس الشبكة المحلية، وأن جدار حماية سطح المكتب يسمح بالاتصال. -\nيُرجى مشاركة أي مشاكل أُخرى مع المطورين. + يُرجى التحقق من اتصال الهاتف المحمول وسطح المكتب بنفس الشبكة المحلية، وأن جدار حماية سطح المكتب يسمح بالاتصال. \nيُرجى مشاركة أي مشاكل أُخرى مع المطوِّرين. لا يمكن إرسال الرسالة تفضيلات الدردشة المحدّدة تحظر هذه الرسالة. التفاصيل @@ -1919,7 +1912,7 @@ رُفع القطع متصل الخوادم المتصلة - جارِ الاتصال + يتصل الاتصالات النشطة ملف التعريف الحالي أخطاء الحذف @@ -1988,7 +1981,7 @@ المكالمات ممنوعة! لا يمكن مكالمة أحد أعضاء المجموعة لا يمكن إرسال رسالة إلى عضو المجموعة - جارِ الاتصال بجهة الاتصال، يُرجى الانتظار أو التحقق لاحقًا! + يتصل بجهة الاتصال، يُرجى الانتظار أو التحقق لاحقًا! جهات الاتصال المؤرشفة ادعُ لا توجد جهات اتصال مُصفاة @@ -1998,7 +1991,7 @@ يُرجى الطلب من جهة اتصالك تفعيل المكالمات. حذف %d رسائل الأعضاء؟ سيتم وضع علامة على الرسائل للحذف. سيتمكن المُستلم/(المُستلمون) من الكشف عن هذه الرسائل. - حدد + حدّد سيتم حذف الرسائل لجميع الأعضاء. سيتم وضع علامة على الرسائل على أنها تحت الإشراف لجميع الأعضاء. الرسالة @@ -2030,7 +2023,6 @@ ادعُ خيارات الوسائط الجديدة شغّل من قائمة الدردشة. - تبديل قائمة الدردشة: يمكنك تغييره في إعدادات المظهر. نزّل الإصدارات الجديدة من GitHub. ترقية التطبيق تلقائيًا @@ -2211,7 +2203,7 @@ الإشعارات والبطارية فقط مالكي الدردشة يمكنهم تغيير التفضيلات. الخصوصية لعملائك. - الجوالات عن بُعد + الجوّالات عن بُعد ادعُ للدردشة مغادرة المجموعة؟ سيتم إزالة العضو من الدردشة - لا يمكن التراجع عن هذا! @@ -2352,12 +2344,11 @@ إلغاء حظر الأعضاء للجميع؟ حظر الأعضاء للجميع؟ سيتم عرض رسائل من هؤلاء الأعضاء! - لا يمكن قراءة عبارة المرور في Keystore، يُرجى إدخالها يدويًا. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطورين. + لا يمكن قراءة عبارة المرور في Keystore، يُرجى إدخالها يدويًا. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطوِّرين. سيتم إزالة الأعضاء من المجموعة - لا يمكن التراجع عن هذا! المشرفين - لا يمكن قراءة عبارة المرور في Keystore. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطورين. + لا يمكن قراءة عبارة المرور في Keystore. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطوِّرين. موافقة الانتظار - ضبّط مُشغلي الخادم سياسة الخصوصية وشروط الاستخدام. لا يمكن الوصول إلى الدردشات الخاصة والمجموعات وجهات اتصالك لمشغلي الخادم. باستخدام SimpleX Chat، توافق على:\n- إرسال المحتوى القانوني فقط في المجموعات العامة.\n- احترام المستخدمين الآخرين – لا سبام. @@ -2515,7 +2506,7 @@ افتح الرابط النظيف افتح الرابط الكامل أزل تتبع الروابط - رابط مُرحل SimpleX + عنوان مُرحل SimpleX خطأ في وضع علامة \"مقروءة\" البصمة في عنوان الخادم الوجهة لا تتطابق مع الشهادة: %1$s. البصمة في عنوان خادم التحويل لا تتطابق مع الشهادة: %1$s. @@ -2542,4 +2533,125 @@ فيديوهات رسائل صوتية إذا انضممت إلى قنوات أو أنشأتها، فستتوقف عن العمل نهائيًا. + %1$d/%2$d مُرحلات نشطة + %1$d/%2$d مُرحلات نشطة، %3$d فشلت + %1$d/%2$d مُرحلات متصلة + %1$d/%2$d مُرحلات متصلة، %3$d خطأ + %1$d مشترك + %1$d مشترك + وافقت + نشط + احظر المشترك للكل؟ + مُرحلات الدردشة + مُرحلات الدردشة + احذف القناة + احذف القناة؟ + حُذفت + حُذفت القناة + احذف المُرحل + إذاعة + إلغاء إنشاء القناة؟ + اختبر المُرحل لاسترداد اسمه.]]> + %1$s !]]> + قناة + قناة + قناة + اسم القناة بالكامل + رابط القناة + أعضاء القناة + اسم القناة + يُخزّن ملف تعريف القناة على أجهزة المشتركين وعلى مُرحلات الدردشة. + حُدِّث ملف تعريف القناة + ستُحذف القناة لجميع المشتركين - لا يمكن التراجع عن هذا الإجراء! + ستُحذف القناة من عِندك - لا يمكن التراجع عن هذا الإجراء! + ستبدأ القناة بالعمل مع %1$d من أصل %2$d من المُرحلات. أتود المتابعة؟ + مُرحل الدردشة + مُرحلات الدردشة + مُرحلات الدردشة توجّه الرسائل في القنوات التي تنشئها. + مُرحلات الدردشة توجّه الرسائل في القنوات في القناة. + تحقق من عنوان المُرحل وحاول مرة أخرى. + تحقق من اسم المُرحل وحاول مرة أخرى. + اضبط المُرحلات + اتصل + متصل + يتصل + أنشئ قناة عامة + أنشئ قناة عامة + أنشئ قناة عامة (تجريبي) + ينشئ قناة + %d أحداث القناة + فك ترميز الرابط + تم الإسقاط (%1$d محاولات) + حرّر ملف تعريف القناة + فعّل مُرحل دردشة واحد على الأقل لإنشاء قناة. + أدخل اسم المُرحل… + خطأ في إضافة المُرحل + خطأ في إنشاء القناة + خطأ في فتح القناة + خطأ: %s + خطأ في حفظ ملف تعريف القناة + فشل + فشل + احصل على الرابط + عنوان المُرحل غير صالح! + اسم المُرحل غير صالح! + مدعو + انضم للقناة + غادِر القناة + مغادرة القناة؟ + الرابط + رسالة خطأ + جديد + مُرحل دردشة جديد + لا مُرحلات دردشة + لا مُرحلات دردشة مفعّلة. + ليس كل المُرحلات متصلة + افتح قناة + افتح قناة جديدة + المالك + المالكون + عنوان المُرحل مسبق الضبط + اسم المُرحل مسبق الضبط + مُرحل + مُرحل + عنوان المُرحل + عنوان المُرحل + فشل اتصال المُرحل + رابط المُرحل + فشل اختبار المُرحل + أزِل المشترك + إزالة المشترك؟ + احفظ وأرسل إشعار للمشتركين في القناة + احفظ ملف تعريف القناة + يتطلب الخادم تفويضًا للاتصال بالمُرحل، يُرجى التحقق من كلمة المرور. + تحذير من الخادم + شارك عنوان المُرحل + مشترك + المشتركون + يستخدم المشتركون رابط المُرحل للاتصال بالقناة.\nاُستخدم عنوان المُرحل لإعداد هذا المُرحل للقناة. + ستُزيل المشترك من القناة - لا يمكن التراجع عن هذا الإجراء! + انقر انضم للقناة + فشل الاختبار عند الخطوة %s. + اختبر المُرحل + أُزيل التطبيق هذه الرسالة بعد %1$d محاولات لاستلامها. + عنوان مُرحل الدردشة هذا لا يمكن استخدامه للاتصال. + إلغاء حظر المشترك للجميع؟ + ملف القناة التعريفي حُدِّث + استخدم للقنوات الجديدة + استخدم مُرحل + تحقق + عبر %1$s + تسجيل الصوت غير مدعوم على منصتك + انتظر + رد الانتظار + أنت + أنت مشترك + بإمكانك مشاركة رابط أو رمز QR - وسيتمكن أي شخص من الانضمام إلى القناة. + لقد اتصلت بالقناة عبر رابط المُرحل هذا. + قناتك + قناتك + سيتم مشاركة ملف تعريفك %1$s مع مُرحلات القناة والمشتركين.\nيمكن للمُرحلات الوصول إلى رسائل القناة. + عنوان مُرحلك + اسم مُرحلك + ستتوقف عن تلقي الرسائل من هذه القناة، وسيتم الاحتفاظ بسجل الدردشة. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index a9b2045b97..b21f1b5627 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -74,6 +74,7 @@ end-to-end encryption.]]> end-to-end encryption with perfect forward secrecy, repudiation and break-in recovery.]]> quantum resistant e2e encryption with perfect forward secrecy, repudiation and break-in recovery.]]> + not end-to-end encrypted. Chat relays can see these messages.]]> This chat is protected by end-to-end encryption. This chat is protected by quantum resistant end-to-end encryption. @@ -468,7 +469,7 @@ Talk to someone Let someone connect to you Connect via link or QR code - Connect with someone + Create your link Invite someone privately A link for one person to connect Create your public address @@ -1066,7 +1067,7 @@ for each contact and group member.\nPlease note: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.]]> Update transport isolation mode? Use .onion hosts to No if SOCKS proxy does not support them.]]> - Please note: message and file relays are connected via SOCKS proxy. Calls and sending link previews use direct connection.]]> + Please note: message and file relays are connected via SOCKS proxy. Calls use direct connection.]]> Private routing Always Unknown servers @@ -1291,9 +1292,27 @@ Make a private connection Migrate from another device How it works + Be free\nin your network + Private and secure messaging. + The first network where you own\nyour contacts and groups. + Get started + Why SimpleX is built. + Your profile + On your phone, not on servers. + No account. No phone. No email. No ID.\nThe most secure encryption. + Enter profile name… + Migrate - - How SimpleX works + + You were born without an account. + Nobody tracked your conversations. No one drew a map of where you\'d been. Privacy was never a feature — it was the way of life. + Then we moved online, and every platform asked for a piece of you — your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way — telephone, email, messengers, social media. It seemed the only way possible. + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Not a better lock on someone else\'s door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it — you are sovereign. + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + The oldest human freedom — to speak to another person without being watched — built on infrastructure that cannot betray it. + Because we destroyed the power to know who you are. So that your power can never be taken. + Be free in your network. To protect your privacy, SimpleX uses separate IDs for each of your contacts. Only client devices store user profiles, contacts, groups, and messages. end-to-end encrypted, with post-quantum security in direct messages.]]> @@ -1320,11 +1339,10 @@ Use random passphrase - Private chats, groups and your contacts are not accessible to server operators. - By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam. + Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code + You commit to:\n- Only legal content in public groups\n- Respect other users — no spam Privacy policy and conditions of use. Accept - Configure server operators Server operators Network operators SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. @@ -1341,6 +1359,15 @@ Update Continue + + Your network + Network routers cannot know\nwho talks to whom + Setup routers + Setup notifications + + + Network commitments + Incoming video call Incoming audio call @@ -1376,6 +1403,7 @@ Open SimpleX Chat to accept call Enable calls from lock screen via Settings. Open + Open external link? e2e encrypted @@ -1673,7 +1701,8 @@ Confirm database upgrades Reachable app toolbars Reachable chat toolbar - Toggle chat list: + Bottom bar + Top bar You can change it in Appearance settings. Show console in new window Show chat list in new window @@ -2335,6 +2364,35 @@ History is not sent to new members. Members can report messsages to moderators. Reporting messages is prohibited in this group. + Chat with admins + Allow members to chat with admins. + Prohibit chats with admins. + Members can chat with admins. + Chats with admins are prohibited. + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Enable chats with admins? + Enable + + + Subscriber reports + Allow sending direct messages to subscribers. + Prohibit sending direct messages to subscribers. + Send up to 100 last messages to new subscribers. + Do not send history to new subscribers. + Subscribers can send disappearing messages. + Subscribers can send direct messages. + Direct messages between subscribers are prohibited. + Subscribers can irreversibly delete sent messages. (24 hours) + Subscribers can add message reactions. + Subscribers can send voice messages. + Subscribers can send files and media. + Subscribers can send SimpleX links. + Subscribers can report messsages to moderators. + Up to 100 last messages are sent to new subscribers. + History is not sent to new subscribers. + Allow subscribers to chat with admins. + Subscribers can chat with admins. + Delete after %d sec %ds @@ -2371,6 +2429,7 @@ Chats with members No chats with members + Chats with members are disabled Delete chat Delete chat with member? @@ -2587,7 +2646,7 @@ Easier to invite your friends 👋 We made connecting simpler for new users. Safe web links - - opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking. + - opt-in to send link previews.\n- use SOCKS proxy if enabled.\n- prevent hyperlink phishing.\n- remove link tracking. Non-profit governance To make SimpleX Network last. View updated conditions @@ -2976,7 +3035,7 @@ Network error Error Cancel creating channel? - Cancel + Your new channel %1$s is connected to %2$d of %3$d relays.\nIf you cancel, the channel will be deleted - you can create it again. Enable at least one chat relay to create a channel. Your profile %1$s will be shared with channel relays and subscribers.\nRelays can access channel messages. Configure relays @@ -2984,8 +3043,7 @@ Relay connection failed Not all relays connected Wait - Proceed - Channel will start working with %1$d of %2$d relays. Proceed? + Channel will start working with %1$d of %2$d relays. Continue? Relay address @@ -3002,7 +3060,7 @@ Enable link previews? Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Link preview will be requested via SOCKS proxy. DNS lookup may still happen locally via your DNS resolver. Enable Disable - \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 03a8bcfdc5..c691447b32 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -764,7 +764,6 @@ Информация Инсталирай SimpleX Chat за терминал Как работи - Как работи SimpleX Защитен от спам Игнорирай Покани членове @@ -1038,7 +1037,7 @@ SimpleX Лого SimpleX Екип SMP сървъри - Сподели адреса с контактите\? + Сподели адреса с контактите? Сподели линк Сподели с контактите Спри споделянето @@ -2040,7 +2039,6 @@ Пропусни тази версия Провери за актуализации БАЗА ДАННИ - Превключване на чат списъка: Можете да изпращате съобщения до %1$s от архивираните контакти. Достъпен панел Изпращането на съобщения на груповия член не е налично @@ -2328,7 +2326,6 @@ Чат с член Разговаряйте с членовете, преди да се присъединят. Нарушение на правилата на общността - Конфигуриране на сървърни оператори Свързване Свържете се по-бързо! 🚀 Връзката е блокирана diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index 5c8f73bf93..7ab3f5a381 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -1216,7 +1216,6 @@ Sense identificadors d\'usuari. Per protegir la vostra privadesa SimpleX utilitza identificadors separats per a cadascun dels vostres contactes.. Obrir SimpleX - Com funciona SimpleX Només els dispositius client emmagatzemen perfils d\'usuari, contactes, grups i missatges. Notificacions privades Com afecta la bateria @@ -1975,7 +1974,6 @@ Barres d\'eines d\'aplicacions accessible Barres d\'eines de xat accessible Mostra la llista de xat en una finestra nova - Commuta la llista de xat: Mostrar consola en finestra nova Podeu canviar-la a la configuració de l\'aparença. Actualitzar i obrir el xat @@ -2342,7 +2340,6 @@ Els xats privats, els grups i els vostres contactes no són accessibles per als operadors de servidor. Acceptar En utilitzar SimpleX Chat accepteu:\n- enviar només contingut legal en grups públics.\n- Respectar els altres usuaris, sense correu brossa. - Configurar els operadors de servidor Enllaç al canal SimpleX Aquest enllaç requereix una versió de l\'aplicació més recent. Actualitzeu l\'aplicació o demaneu al vostre contacte que enviï un enllaç compatible. Enllaç de connexió no compatible diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 43afc3be04..df4907885c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -242,7 +242,6 @@ probíhající hovor Decentralizovaná Jak to funguje - Jak funguje SimpleX Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy. Soukromé oznámení Pravidelné @@ -877,7 +876,7 @@ Předvolby skupiny Přímé zprávy Mazání všem - zapnuty + zapnuto povoleno vám vypnuty Mizící zprávy zakázány. @@ -930,7 +929,7 @@ moderované moderovaný %s Smazat zprávu člena\? - moderovaný + Moderovat Kontaktujte prosím správce skupiny. jste pozorovatel pozorovatel @@ -1168,7 +1167,7 @@ BARVY MOTIVU Přizpůsobit motiv Aktualizace profilu bude zaslána vašim kontaktům. - Sdílet adresu s kontakty\? + Sdílet adresu s kontakty? Přestat sdílet adresu\? Vytvořit adresu, aby se s vámi lidé mohli spojit. Uložit nastavení SimpleX adresy @@ -1543,7 +1542,7 @@ blokováno %s kontakt %1$s změnen na %2$s Vytvořeno v - Blok všem + Blokovat všem Blokovat člena všem? Chyba blokování člena všem Vylepšené doručovaní zpráv @@ -2054,7 +2053,7 @@ Žádné servery pro soukromé směrování chatů. Žádné servery pro příjem souborů. Žádné servery pro příjem zpráv. - Všechny chaty budou ze seznamu odebrány %s, a seznam bude smazán + Všechny chaty budou ze seznamu %s odebrány, a seznam bude smazán Pro sociální sítě Vzdálené telefony %s.]]> @@ -2247,7 +2246,6 @@ Nastavit výchozí téma Nahráno Ano - Přepnout chat seznam: Tuto akci nelze zrušit - zprávy odeslané a přijaté v tomto chatu dříve než vybraná, budou smazány. Statistiky serverů budou obnoveny - nemůže být vráceno! Odešlete soukromý report @@ -2366,7 +2364,6 @@ Členové budou odstraněny z chatu - toto nelze zvrátit! Použitím SimpleX chatu souhlasíte že:\n- ve veřejných skupinách budete zasílat pouze legální obsah.\n- budete respektovat ostatní uživatele – žádný spam. Přijmout - Nastavit operátora serveru Zásady ochrany soukromí a podmínky používání. Soukromé konverzace, skupiny a kontakty nejsou přístupné provozovatelům serverů. Nepodporovaný odkaz k připojení @@ -2550,5 +2547,13 @@ selhal Pokud jste se připojili k nějakým kanálům nebo je vytvořili, přestanou trvale fungovat. aktivní - Zrušit + Narodili jste se bez účtu. + Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života. + Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob. + Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen. + Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén. + Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné. + Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit. + Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít. + Buďte svobodní ve své síti. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index c66d351601..8700ade74e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -28,7 +28,7 @@ verbindung %1$d verbindung hergestellt für eine Verbindung eingeladen - verbinde… + Verbinde… sie haben einen Einmal-Link geteilt sie haben einen Einmal-Link inkognito geteilt über einen Gruppen-Link @@ -80,7 +80,7 @@ Der Server erfordert zum Erstellen von Warteschlangen eine Autorisierung. Bitte überprüfen Sie das Passwort. Fingerabdruck in der Serveradresse stimmt nicht mit dem Zertifikat überein. Verbinde - Erzeuge Warteschlange + Warteschlange erstellen Sichere Warteschlange Lösche Warteschlange Trenne Verbindung @@ -172,11 +172,11 @@ Willkommen! Dieser Text ist in den Einstellungen verfügbar. Chats - verbinde … + Verbinde… Sie sind zu der Gruppe eingeladen Beitreten als %s - verbinde … - Zum Starten eines neuen Chats tippen + Verbinde… + Tippen, um einen neuen Chat zu starten Chatten Sie mit den Entwicklern Sie haben keine Chats @@ -469,11 +469,10 @@ Sie entscheiden, wer sich mit Ihnen verbinden kann. Dezentral Jeder kann seine eigenen Server aufsetzen. - Erstellen Sie Ihr Profil + Ihr Profil erstellen Stellen Sie eine private Verbindung her Wie es funktioniert - Wie SimpleX funktioniert SimpleX nutzt individuelle Kennungen für jeden Ihrer Kontakte, um Ihre Privatsphäre zu schützen. Nur die Endgeräte speichern Benutzerprofile, Kontakte, Gruppen und Nachrichten. GitHub-Repository mehr dazu.]]> @@ -547,7 +546,7 @@ Privatsphäre App-Bildschirm schützen Bilder automatisch akzeptieren - Link-Vorschau senden + Linkvorschau senden App-Datensicherung MEINE DATEN @@ -695,7 +694,7 @@ Die Gruppeneinladung ist abgelaufen hat %1$s eingeladen. - verbunden + Verbunden hat die Gruppe verlassen änderte die Rolle von %s auf %s änderte Ihre Rolle auf %s @@ -726,13 +725,13 @@ Gruppe gelöscht Eingeladen Verbindung (erstellt) - Verbinde (nach einer Einladung) + Verbindung (nach einer Einladung) Verbindung (angenommen) Verbindung (angekündigt) Verbunden Vollständig Ersteller - verbinden + Verbinde Keine Kontakte zum Hinzufügen Neue Mitgliedsrolle @@ -757,7 +756,7 @@ Gruppe verlassen Gruppenprofil bearbeiten Gruppen-Link - Link erzeugen + Link erstellen Link löschen? Link löschen Sie können diesen Link oder QR-Code teilen – damit kann jede Person der Gruppe beitreten. Wenn Sie den Link später löschen, werden Sie keine Gruppenmitglieder verlieren, die der Gruppe darüber beigetreten sind. @@ -944,7 +943,7 @@ Sicherheits-Gutachten Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft. Was ist neu - Administratoren können Links für den Beitritt zu Gruppen erzeugen. + Administratoren können Links für den Beitritt zu Gruppen erstellen. Kontaktanfragen automatisch annehmen Vergleichen Sie die Sicherheitscodes mit Ihren Kontakten. App-Bildschirm in aktuellen Anwendungen verbergen. @@ -1040,7 +1039,7 @@ Für die Anzeige das Passwort im Suchfeld eingeben Privates Profil erzeugen! Stummschalten - Zum Aktivieren des Profils tippen. + Tippen, um das Profil zu aktivieren. Stummschaltung aufheben Bei Inaktivität stummgeschaltet! Schützen Sie Ihre Chat-Profile mit einem Passwort! @@ -1191,20 +1190,20 @@ Sie werden Ihre damit verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. Design anpassen INTERFACE-FARBEN - 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. + Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre SimpleX-Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre SimpleX-Kontakte gesendet. Alle Ihre Kontakte bleiben verbunden. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können. SimpleX-Adresse erstellen - Mit Kontakten teilen + Mit SimpleX-Kontakten teilen Ihre Kontakte bleiben weiterhin verbunden. Automatisch akzeptieren Geben Sie eine Begrüßungsmeldung ein … (optional) Freunde einladen Lassen Sie uns über SimpleX Chat schreiben - Profil-Aktualisierung wird an Ihre Kontakte gesendet. + Profil-Aktualisierung wird an Ihre SimpleX-Kontakte gesendet. SimpleX-Adress-Einstellungen speichern Einstellungen speichern\? - Die Adresse mit Kontakten teilen\? + Die Adresse mit SimpleX-Kontakten teilen? Teilen beenden Das Teilen der Adresse beenden\? Keine Adresse erstellt @@ -1398,7 +1397,7 @@ Das Senden von Bestätigungen ist für %d Gruppen deaktiviert Das Senden von Bestätigungen ist für %d Gruppen aktiviert Für alle Gruppen deaktivieren - deaktiviert + Deaktiviert Bestätigungen sind deaktiviert Keine Information über die Zustellung Zustellung @@ -1438,7 +1437,7 @@ Datenbank-Ordner öffnen Das Passwort wird in Klartext in den Einstellungen gespeichert, nachdem Sie es geändert oder die App neu gestartet haben. Das Passwort wurde in Klartext in den Einstellungen gespeichert. - Bitte beachten Sie: Die Nachrichten- und Datei-Relais sind per SOCKS-Proxy verbunden. Anrufe und gesendete Link-Vorschaubilder nutzen eine direkte Verbindung.]]> + Bitte beachten Sie: Die Nachrichten- und Datei-Relais sind per SOCKS-Proxy verbunden. Anrufe nutzen eine direkte Verbindung.]]> Lokale Dateien verschlüsseln Öffnen Gespeicherte Dateien & Medien verschlüsseln @@ -1511,7 +1510,7 @@ Fehler bei der Neuverhandlung der Verschlüsselung Neuverhandlung der Verschlüsselung fehlgeschlagen Gruppenmitglieder blockieren - Erstellen Sie eine Gruppe mit einem zufälligen Profil. + Gruppe mit einem zufälligen Profil erstellen. Verbundener Desktop Desktop-Adresse Bessere Gruppen @@ -1607,7 +1606,7 @@ Kontakt hinzufügen Zum Scannen tippen Behalten - Zum Link einfügen tippen + Tippen, um den Link einzufügen Suchen oder SimpleX-Link einfügen Der Chat wurde gestoppt. Wenn diese Datenbank bereits auf einem anderen Gerät von Ihnen verwendet wurde, sollten Sie diese dorthin zurück übertragen, bevor Sie den Chat starten. Chat starten? @@ -1928,7 +1927,7 @@ Bitte überprüfen Sie, ob sich das Mobiltelefon und die Desktop-App im gleichen lokalen Netzwerk befinden, und die Desktop-Firewall die Verbindung erlaubt. \nBitte teilen Sie weitere mögliche Probleme den Entwicklern mit. Nachricht wurde nicht gesendet - Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. + Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt. Bitte versuchen Sie es später erneut. Fehler beim privaten Routing Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird. @@ -1990,7 +1989,7 @@ Daten-Pakete heruntergeladen Verbundene Server Gelöscht - deaktiviert + Deaktiviert Fehler beim Wiederherstellen der Verbindung zum Server Nachricht weitergeleitet Dateien @@ -2001,7 +2000,7 @@ Dateispeicherort öffnen andere Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel: %1$s. - Link scannen / einfügen + Link einfügen / Scannen Alle Server neu verbinden Server neu verbinden? Alle Server neu verbinden? @@ -2108,7 +2107,6 @@ Erstellen Laden Sie neue Versionen von GitHub herunter. Direkt aus der Chat-Liste abspielen. - Chat-Liste umschalten: Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden. Kontakte für spätere Chats archivieren. Ihre IP-Adresse und Verbindungen werden geschützt. @@ -2307,7 +2305,7 @@ Schutz der Privatsphäre Ihrer Kunden. Zur Verbindung aufgefordert Bitte verkleinern Sie die Nachrichten-Größe oder entfernen Sie Medien und versenden Sie diese erneut. - Nur Chat-Eigentümer können die Präferenzen ändern. + Präferenzen können nur von Chat-Eigentümern geändert werden. Bitte verkleinern Sie die Nachrichten-Größe und versenden Sie diese erneut. Die Rolle wird auf %s geändert. Im Chat wird Jeder darüber informiert. Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten. @@ -2447,15 +2445,14 @@ Moderatoren Mitglieder für Alle blockieren? Alle neuen Nachrichten dieser Mitglieder werden nicht angezeigt! - Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam. + Sie verpflichten sich dazu:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam. Datenschutz- und Nutzungsbedingungen. Annehmen - Server-Betreiber konfigurieren - Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. + Betreiber verpflichten sich:\n- Unabhängig zu bleiben\n- Metadaten auf ein Minimum zu reduzieren\n- Geprüften Open‑Source‑Code einzusetzen Verbindungs-Link wird nicht unterstützt Verkürzter Link Vollständiger Link - SimpleX-Kanal-Link + SimpleX-Kanallink Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. Alle Server Aus @@ -2633,25 +2630,24 @@ Fehlgeschlagen Kanäle, welche Sie erstellt haben oder denen Sie beigetreten sind, werden dauerhaft deaktiviert. %1$d/%2$d Relais aktiv - %1$d/%2$d Relais aktiv, %3$d Fehlgeschlagen + %1$d/%2$d Relais aktiv, %3$d fehlgeschlagen %1$d/%2$d Relais verbunden %1$d/%2$d Relais verbunden, %3$d Fehler %1$d Abonnent %1$d Abonnenten Angenommen Aktiv - Abonnenten für alle blockieren? + Abonnent für alle blockieren? Broadcast - Abbrechen Kanalerstellung abbrechen? - Relais testen, um dessen Namen zu ermitteln.]]> + Relais testen, um dessen Namen abzurufen.]]> %1$s!]]> Kanal Kanal Kanal - Kanal-Link + Kanallink Kanal-Mitglieder - Kanal-Name + Kanalname Der Kanal wird für alle Abonnenten gelöscht. Dies kann nicht rückgängig gemacht werden! Der Kanal wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! Der Kanal wird mit %1$d von %2$d Relais gestartet. Fortfahren? @@ -2665,11 +2661,11 @@ Relais-Name überprüfen und erneut versuchen. Relais konfigurieren Verbinden - verbunden - verbinden - Kanal erstellen - Kanal erstellen - Kanal erstellen (BETA) + Verbunden + Verbinde + Öffentlichen Kanal erstellen + Öffentlichen Kanal erstellen + Öffentlichen Kanal erstellen (BETA) Kanal wird erstellt Link dekodieren Kanal löschen @@ -2703,7 +2699,6 @@ Eigentümer Voreingestellte Relais-Adresse Voreingestellter Relais-Name - Fortfahren Relais RELAIS Relais-Adresse @@ -2738,8 +2733,144 @@ Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden. Ihr Kanal Ihr Kanal - Ihr Profil %1$s wird mit den Kanal‑Relais und -Abonnenten geteilt. + Ihr Profil %1$s wird mit den Kanal‑Relais und -Abonnenten geteilt.\nRelais können auf Kanalnachrichten zugreifen. Ihre Relais-Adresse Ihr Relais-Name Sie werden keine Nachrichten mehr aus diesem Kanal erhalten. Der Chatverlauf bleibt erhalten. + Vollständiger Name des Kanals: + Das Kanalprofil wird auf den Geräten der Abonnenten und auf den Chat‑Relais gespeichert. + Kanalprofil wurde aktualisiert + %d Kanalereignisse + Kanal gelöscht + Verworfen (%1$d Versuche) + Fehler: %s + Fehler beim Speichern des Kanalprofils + Übertragungsfehler + Speichern und Abonnenten des Kanals informieren + Kanalprofil speichern + Die App hat diese Nachricht nach %1$d Empfangsversuchen entfernt. + Kanalprofil aktualisiert + %1$d/%2$d Relais aktiv, %3$d Fehler + %1$d/%2$d Relais aktiv, %3$d entfernt + %1$d/%2$d Relais verbunden, %3$d fehlgeschlagen + %1$d/%2$d Relais verbunden, %3$d entfernt + %1$d Relais fehlgeschlagen + %1$d Relais nicht aktiv + %1$d Relais entfernt + Das Hinzufügen von Relais wird zu einem späteren Zeitpunkt unterstützt. + Alle Relais fehlgeschlagen + Alle Relais entfernt + Broadcast nicht möglich + Der Kanal hat keine aktiven Relais. Bitte später erneut versuchen. + Der Kanal ist vorübergehend nicht erreichbar + Inaktiv + Keine aktiven Relais + Vom Betreiber entfernt + Warte auf das Hinzufügen von Relais durch den Eigentümer des Kanals. + Geschäftliche Adresse + Kanallink + Kontaktadresse + Deaktivieren + Aktivieren + Linkvorschau aktivieren? + Fehler + Fehler beim Teilen des Kanals + (vom Eigentümer) + Gruppen-Link + Linksignatur erfolgreich überprüft. + Netzwerk-Fehler + Einmal-Link + Relay‑Status: + Das Senden einer Link-Vorschau kann Ihre IP‑Adresse an die Website übermitteln. Sie können dies später in den Datenschutzeinstellungen ändern. + Kanal teilen… + Per Chat teilen + ⚠️ Signaturüberprüfung fehlgeschlagen: %s. + (signiert) + Zum Öffnen tippen + Die Verbindung hat das Limit für nicht zugestellte Nachrichten erreicht + Kanal-Präferenzen + Kanal-Präferenzen können nur von Kanal-Eigentümern geändert werden. + Verbindungs-Link für eine Person + Kanäle + Über einen Link oder QR-Code verbinden + Ihren Link erstellen + Ihre öffentliche Adresse erstellen + Freunde einladen – jetzt noch einfacher 👋 + Damit Sie jeder erreichen kann + Für privaten Chat einladen + Jemand mit Ihnen verbinden lassen + Neuer Einmal-Link + Non‑Profit‑Governance + - Opt‑in zum Senden von Linkvorschauen.\n- SOCKS-Proxy verwenden, falls aktiviert.\n- Hyperlink‑Phishing verhindern.\n- Link‑Tracking entfernen. + Oder den QR‑Code persönlich oder per Videoanruf zeigen. + Oder diesen QR‑Code verwenden – ausgedruckt oder online. + Volle Kontrolle: Sie können Ihre eigenen Relais betreiben. + Privatsphäre: für Besitzer und Abonnenten. + Öffentliche Kanäle – frei sprechen 🚀 + Zuverlässigkeit: mehrere Relais pro Kanal. + Sichere Web-Links + Sicherheit: Eigentümer besitzen die Kanalschlüssel. + Den Link über einen beliebigen Messenger versenden – es ist sicher. Bitte in SimpleX einfügen. + Mit Jemandem sprechen + Für ein dauerhaftes SimpleX-Netzwerk. + Diese Adresse in Ihrem Social‑Media‑Profil, auf Ihrer Webseite oder in Ihrer E‑Mail‑Signatur verwenden. + Wir haben das Verbinden für neue Nutzer vereinfacht. + Ihre öffentliche Adresse + Sie wurden ohne eine Benutzerkennung geboren. + Niemand verfolgte Ihre Gespräche. Niemand erstellte eine Karte, wo Sie sich aufgehalten haben. Privatsphäre war nie ein Feature - sie war selbstverständlich. + Dann sind wir online gegangen, und jede Plattform wollte Etwas von Ihnen - Ihren Namen, Ihre Nummer, Ihre Freunde. Wir akzeptierten, dass es der Preis mit Anderen zu kommunizieren ist, Jemandem preiszugeben, mit wem und wie wir miteinander kommunizieren. Jede Generation, Menschen und Technologien, kannten es nur so - Telefon, E-Mail, Messenger, soziale Medien. Es schien der einzig mögliche Weg zu sein. + Es gibt einen anderen Weg. Ein Netzwerk ohne Telefonnummern, ohne Benutzernamen, ohne Benutzerkennungen und ohne jegliche Benutzeridentität. Ein Netzwerk, welches Menschen verbindet und verschlüsselte Nachrichten überträgt, ohne zu wissen, wer mit wem verbunden ist. + Nicht ein besseres Schloss an der Tür eines Anderen. Kein freundlicher Vermieter, der Ihre Privatsphäre respektiert, aber dennoch jeden Besucher registriert. Sie sind kein Gast. Sie sind zu Hause. Kein Vermieter, kein Fremder kann es betreten - Sie sind souverän. + Ihre Kommunikation gehört Ihnen, so wie es immer war, bevor es das Internet gab. Das Netzwerk ist kein Ort, den Sie besuchen. Es ist ein Ort, den Sie erschaffen und besitzen und Niemand kann es Ihnen nehmen, egal ob Sie es privat oder öffentlich machen. + Die älteste Freiheit des Menschen - mit einem anderen Menschen sprechen zu können, ohne beobachtet zu werden - gestützt auf einer Infrastruktur, die Sie nicht verraten kann. + Weil wir die Macht zerstört haben, zu wissen, wer Sie sind. Damit Ihnen Ihre Macht niemals genommen werden kann. + Genießen Sie die Freiheit in Ihrem Netzwerk. + + Abonnenten-Meldungen + Das Senden von Direktnachrichten an Abonnenten erlauben. + Das Senden von Direktnachrichten an Abonnenten nicht erlauben. + Bis zu 100 der letzten Nachrichten an neue Abonnenten senden. + Den Nachrichtenverlauf nicht an neue Abonnenten senden. + Abonnenten können verschwindende Nachrichten versenden. + Abonnenten können Direktnachrichten versenden. + Direktnachrichten zwischen Abonnenten sind nicht erlaubt. + Abonnenten können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) + Abonnenten können eine Reaktion auf Nachrichten geben. + Abonnenten können Sprachnachrichten versenden. + Abonnenten können Dateien und Medien versenden. + Abonnenten können SimpleX-Links versenden. + Abonnenten können Nachrichten an Moderatoren melden. + Bis zu 100 der letzten Nachrichten werden an neue Abonnenten gesendet. + Der Nachrichtenverlauf wird nicht an neue Abonnenten gesendet. + Mitgliedern den Chat mit Administratoren erlauben. + Abonnenten den Chat mit Administratoren erlauben. + Seien Sie frei\nin Ihrem Netzwerk + Chats mit Administratoren sind nicht erlaubt. + Chats mit Administratoren in öffentlichen Kanälen sind nicht Ende‑zu‑Ende‑verschlüsselt – bitte nur über vertrauenswürdige Chat‑Relais nutzen. + Chats mit Mitgliedern sind deaktiviert + Chat mit Administratoren + Aktivieren + Chats mit Administratoren aktivieren? + Geben Sie einen Profilnamen ein… + Jetzt starten + Mitglieder können mit Administratoren chatten. + nicht Ende‑zu‑Ende‑verschlüsselt. Chat‑Relais können sie einsehen.]]> + Migrieren + Netzwerk‑Verpflichtungen + Netzwerk‑Router können nicht erkennen,\nwer mit wem kommuniziert + Kein Account. Keine Telefonnummer. Keine E‑Mail. Keine ID.\nDie sicherste Verschlüsselung. + Auf Ihrem Gerät, nicht auf Servern. + Externen Link öffnen? + Private und sichere Kommunikation. + Chat mit Administratoren nicht erlauben. + Benachrichtigungen einrichten + Router einrichten + Abonnenten können mit Administratoren chatten. + Das erste Netzwerk,\nin dem Sie Ihre Kontakte und Gruppen besitzen. + Warum SimpleX entwickelt wurde. + Ihr Netzwerk + Ihr Profil + Untere Leiste + Die Linkvorschau wird über einen SOCKS-Proxy angefordert. DNS-Abfragen können dennoch lokal über Ihren DNS-Resolver erfolgen. + Obere Leiste diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index 20a271f58f..47cfd90ad6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -549,7 +549,6 @@ Διαμορφωμένοι SMP διακομιστές Διαμορφωμένοι XFTP διακομιστές Διαμορφωμένοι ICE διακομιστές - Διαμόρφωση χειριστών διακομιστή Επιβεβαίωσε Επιβεβαίωση διαγραφής επαφής; Επιβεβαίωση αναβαθμίσεων βάσης δεδομένων @@ -1007,7 +1006,6 @@ Πως επηρεάζει τη μπαταρία Πως βοηθάει την ιδιωτικότητα Πως δουλεύει - Πως δουλεύει το SimpleX Πως να Πως να το χρησιμοποιήσεις Πως να χρησιμοποιήσεις markdown σύνταξη @@ -1387,7 +1385,6 @@ Για να λαμβάνεις ειδοποιήσεις σχετικά με τις νέες εκδόσεις, ενεργοποίησε τον περιοδικό έλεγχο για σταθερές ή δοκιμαστικές εκδόσεις. Για να συνδεθείς μέσω συνδέσμου Για να συνδεθείς, η επαφή σου μπορεί να σαρώσει τον κωδικό QR ή να χρησιμοποιήσει τον σύνδεσμο στην εφαρμογή. - Εναλλαγή λίστας συνομιλιών: Ενεργοποίηση ανώνυμης λειτουργίας κατά τη σύνδεση. Για απόκρυψη ανεπιθύμητων μηνυμάτων. Για να πραγματοποιήσεις κλήσεις, επέτρεψε τη χρήση του μικροφώνου σου. Τερμάτισε την κλήση και προσπάθησε να καλέσεις ξανά. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index c233d8eabc..7088c54d9b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -358,7 +358,7 @@ Los miembros pueden enviar mensajes de voz. en modo incógnito mediante dirección de contacto ¡Error al crear perfil! - No se pudo cargar el chat + Fallo en la carga del chat Fallo en la carga de chats Enlace completo Error al eliminar contacto @@ -381,11 +381,10 @@ Servidores ICE (uno por línea) Nombre completo: Tu decides quién se conecta. - Cómo funciona SimpleX Colgar Archivos y multimedia ¡Grupo no encontrado! - perfil de grupo actualizado + perfil del grupo actualizado Error al crear enlace de grupo Error al eliminar enlace de grupo activado para el contacto @@ -819,7 +818,7 @@ Cambiar servidor de recepción Totalmente descentralizado. Visible sólo para los miembros. Para conectarte mediante enlace - ¡Prueba no superada! + ¡Prueba del servidor no superada! Algunos servidores no han superado la prueba: Usar servidor Para conexiones nuevas @@ -880,7 +879,7 @@ Has sido invitado a un grupo. Únete para conectar con sus miembros. has expulsado a %1$s Tú: %1$s - Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. + Puedes compartir el enlace o código QR. Cualquiera podrá unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. Mis preferencias Con mensaje de bienvenida opcional. @@ -1117,7 +1116,7 @@ Fondo Exportar tema Menús y alertas - Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. + Añade la dirección a tu perfil para que tus contactos SimpleX puedan compartirla con otros. La actualización del perfil se enviará a tus contactos SimpleX. Acerca de la dirección SimpleX Dirección Todos tus contactos permanecerán conectados. La actualización del perfil se enviará a tus contactos. @@ -1134,7 +1133,7 @@ Invitar amigos Hablemos en SimpleX Chat Asegúrate de que el archivo tiene la sintaxis YAML correcta. Exporta el tema para tener un ejemplo de la estructura del archivo de tema. - La actualización del perfil se enviará a tus contactos. + La actualización del perfil se enviará a tus contactos SimpleX. Mensaje recibido Guardar configuración de dirección SimpleX Puedes compartir tu dirección como enlace o código QR para que cualquiera pueda conectarse contigo. @@ -1145,8 +1144,8 @@ ¿Dejar de compartir la dirección\? COLORES DE LA INTERFAZ Puedes crearla más tarde - ¿Compartir la dirección con los contactos\? - Compartir con contactos + ¿Compartir la dirección con los contactos SimpleX? + Compartir con contactos SimpleX Título Puedes compartir esta dirección con tus contactos para que puedan conectar con %s. Tus contactos permanecerán conectados. @@ -1364,7 +1363,7 @@ Abrir Cifra archivos almacenados y multimedia Error al establecer contacto con el miembro - Recuerda: los servidores están conectados mediante proxy SOCKS, pero las llamadas y las previsualizaciones de enlaces usan conexión directa.]]> + Recuerda: los servidores están conectados mediante proxy SOCKS. Las llamadas usan conexión directa.]]> Cifrar archivos locales Nueva aplicación para ordenador! 6 nuevos idiomas para la interfaz @@ -1430,7 +1429,7 @@ Conectar con ordenador Desconectar ¿Bloquear miembro? - %d evento(s) de grupo + %d evento(s) del grupo ¡Nombre no válido! Conectado a móvil Dirección ordenador incorrecta @@ -1909,7 +1908,7 @@ Aún no hay conexión directa, los mensajes son reenviados por el administrador. Otros servidores SMP Otros servidores XFTP - Escanear / Pegar enlace + Pegar enlace / Escanear Mostrar porcentaje Desactivar Desactivado @@ -2041,7 +2040,6 @@ Ningún contacto filtrado Difumina para mayor privacidad Crear - Alternar lista de chats: Ajusta el tamaño de la fuente. Reproduce desde la lista de chats. Actualizar la aplicación automáticamente @@ -2223,7 +2221,7 @@ Sólo los propietarios del chat pueden cambiar las preferencias. El miembro será eliminado del chat. ¡No puede deshacerse! El rol cambiará a %s. Se notificará en el chat. - Dejarás de recibir mensajes del chat. El historial del chat se conserva. + Dejarás de recibir mensajes del chat. El historial del chat se conservará. Cómo ayuda a la privacidad Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién. Tu perfil de chat será enviado a los miembros de chat @@ -2374,9 +2372,8 @@ La frase de contraseña no se ha podido leer en Keystore. Por favor, introdúcela manualmente. Puede deberse a alguna actualización del sistema incompatible con la aplicación. Si no es así, por favor, ponte en contacto con los desarrolladores. Aceptar Política de privacidad y condiciones de uso. - Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. - Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios - spam prohibido. - Configurar operadores de servidores + Los operadores se comprometen a:\n- Ser independientes\n- Minimizar el tratamiento de metadatos\n- Ejecutar código open-source verificado + Te comprometes a:\n- Sólo contenido legal en grupos públicos \n- Respetar a los demás usuarios — no hacer spam Enlace de canal SimpleX Enlace completo Enlace corto @@ -2530,7 +2527,7 @@ Abrir enlace limpio Limpiar enlaces de seguimiento Abrir enlace completo - Enlace de servidor SimpleX + Dirección de servidor SimpleX Error al marcar como leído La huella en la dirección del servidor no coincide con el certificado: %1$s. La huella en la dirección del servidor de destino no coincide con el certificado: %1$s. @@ -2554,4 +2551,251 @@ Buscar mensajes de voz Vídeos Mensajes de voz + %1$d/%2$d servidores activos + %1$d/%2$d servidores activos, %3$d con fallo + %1$d/%2$d servidores conectados + %1$d/%2$d servidores conectados, %3$d errores + aceptado + activo + Test del servidor para recibir su nombre.]]> + El perfil del canal se almacena en los dispositivos de los suscriptores y en los servidores de chat. + El canal comenzará a funcionar con %1$d de %2$d servidores. ¿Continuar? + Servidores de chat + Servidores de chat + Servidores de chat + Servidores de chat + Los servidores de chat reenvían los mensajes en los canales que has creado. + Los servidores de chat reenvían los mensajes a los suscriptores del canal. + Comprueba la dirección del servidor y prueba de nuevo. + Comprueba el nombre del servidor y prueba de nuevo. + Configurar servidores + Conectar + conectado + conectando + Decodificar enlace + eliminado + Eliminar servidor + Activa al menos un servidor de chat para crear un canal. + Introduce el nombre del servidor… + Error al añadir el servidor + La conexión con el servidor ha fallado + ¡El test del servidor ha fallado! + Prueba no superada en el paso %s. + %1$d suscriptor + %1$d suscriptores + ¿Bloquear al suscriptor para todos? + Retransmisión + ¿Cancelar la creación del canal? + %1$s!]]> + canal + Canal + Canal + Título completo: + Enlace del canal + Miembros canal + Título del canal + perfil del canal actualizado + El canal será eliminado para todos los suscriptores. ¡No puede deshacerse! + El canal será eliminado para tí. ¡No puede deshacerse! + CONEXIÓN FALLIDA + Crear canal público + Crear canal público + Crear canal público (BETA) + Creando canal + %d eventos del canal + Eliminar canal + ¿Eliminar el canal? + canal eliminado + caído (%1$d intentos) + Editar perfil del canal + Error al crear el canal + Error al abrir el canal + error:%s + Error al guardar el perfil del canal + fallo + fallo + Recibir el enlace + Si te has unido o has creado canales, dejarán de funcionar permanentemente. + ¡Dirección de servidor no válido! + ¡Nombre de servidor no válido! + Invitado + Unirme al canal + Salir del canal + ¿Salir del canal? + Enlace + Mensaje de error + nuevo + Nuevo servidor de chat + Sin servidores de chat + Ningún servidor de chat activado. + Hay servidores no conectados + Abrir canal + Abrir canal nuevo + PROPIETARIO + Propietarios + Direcciones predefinidas + Nombres predefinidos + servidor + SERVIDOR + Dirección servidor + Dirección del servidor + Enlace servidor + Eliminar suscriptor + ¿Eliminar suscriptor? + Guardar y notificar suscriptores + Guardar perfil del canal + El servidor requiere autorización para conectar con el servidor, comprueba la contraseña. + Alerta del servidor + Compartir dirección del servidor + SUSCRIPTOR + Suscriptores + Los suscriptores usan el enlace del servidor para conectarse a los canales.\nLa dirección del servidor se usó para establecer el servidor para el canal. + El suscriptor será eliminado del canal. ¡No puede deshacerse! + Pulsa Unirme al canal + Test servidor + La app ha eliminado el mensaje tras %1$d intentos de recibirlo. + Esto es una dirección de servidor, no puede usarse para conectar. + ¿Desbloquear al suscriptor para todos? + perfil del canal actualizado + Usar para canales nuevos + Usar servidor + Verificar + mediante %1$s + La grabación de voz no es compatible con tu plataforma + Espera + Espera respuesta + tu + eres suscriptor + Puedes compartir un enlace o código QR. Cualquiera podrá unirse al canal. + Te conectaste al canal mediante este enlace de servidor. + Tu canal + Tu canal + El perfil %1$s será compartido con los servidores de canal y los suscriptores.\nLos servidores tienen acceso a los mensajes del canal. + Tu dirección de servidor + Tu nombre del servidor + Dejarás de recibir mensajes de este canal. El historial del chat se conservará. + fallo + Naciste sin una cuenta. + Nadie monitorizaba tus conversaciones. Nadie registraba tus ubicaciones. La privacidad nunca fue un lujo, era la manera de vivir. + Después pasamos a internet y cada plataforma pedía una parte de tí: tu nombre, tu número, tus amistades. Aceptamos que el precio de hablar con los demás es informar a alguien de quién es interlocutor. Cada generación, personas y tecnología, ha funcionado así: teléfono, email, mensajería, redes sociales. Parecía el único camino. + Existe otro camino. Una red sin números de teléfono. Sin nombres de usuario. Sin cuentas. Sin identificadores de ningún tipo. Una red que conecta las personas y entrega mensajes cifrados sin saber quien está conectado. + No un candado mejorado en la puerta de otro. No un terrateniente que respeta tu privacidad pero sigue guardando un registro de tus visitantes. Tu no eres el invitado. Estás en tu casa y ningún rey podrá entrar. Tu eres el soberano. + Tus conversaciones te pertenecen, tal como ha sido siempre antes de la llegada de internet. Tu red no es un lugar que visitas. Es un lugar que has creado, te pertenece y nadie te la podrá quitar, ya sea pública o privada. + La libertad más antigua del ser humano, la de hablar con otra persona sin ser observado, materializada sobre una infraestructura que no puede traicionarla. + Porque hemos destruido el poder de saber quien eres. De manera que tu poder nunca se pueda arrebatar. + Se libre en tu red. + + Informes de suscriptores + Se permiten mensajes directos entre suscriptores. + No se permiten mensajes directos entre suscriptores. + Se envían hasta 100 mensajes más recientes a los suscriptores nuevos. + No se envía el historial a los suscriptores nuevos. + Los suscriptores del canal pueden enviar mensajes temporales. + Los suscriptores del canal pueden enviar mensajes directos. + Los mensajes directos entre suscriptores del canal no están permitidos. + Los suscriptores del canal pueden eliminar mensajes de forma irreversible. (24 horas) + Los suscriptores pueden añadir reacciones a los mensajes. + Los suscriptores del canal pueden enviar mensajes de voz. + Los suscriptores del canal pueden enviar archivos y multimedia. + Los suscriptores del canal pueden enviar enlaces SimpleX. + Los suscriptores pueden informar de mensajes a los moderadores. + Hasta 100 últimos mensajes son enviados a los suscriptores nuevos. + El historial no se envía a suscriptores nuevos. + %1$d/%2$d servidores activos, %3$d errores + %1$d/%2$d servidores activos, %3$d servidores eliminados + %1$d/%2$d servidores conectados, %3$d con fallo + %1$d/%2$d servidores conectados, %3$d eliminados + %1$d servidores han fallado + %1$d servidores inactivos + %1$d servidores eliminados + Añadir servidores estará disponible en una versión posterior. + Enlace para un solo contacto + Permitir que los miembros chateen con administradores. + Permitir que los suscriptores chateen con administradores. + Todos los servidores han fallado + Todos los servidores eliminados + Se libre\nen tu red + Dirección empresarial + no puedes retransmitir + El canal no tiene servidores activos. Por favor, intenta unirte más tarde. + Enlace del canal + Preferencias del canal + Canales + Canales no disponibles temporalmente + Chat con administradores no permitido + El chat con administradores en el canal público no dispone de cifrado E2E. Úsalo sólo con servidores de confianza. + Chats con miembros desactivado + Chat con administradores + Conecta vía enlace o QR + Crea tu enlace + Dirección de contacto + Crea tu dirección pública + Desactivar + Invitar a tus amigos es más fácil 👋 + Activar + Activar + ¿Activar chat con administradores? + ¿Activar previsualización de enlaces? + Introduce el nombre del perfil… + Error + Error al compartir el canal + Para que cualquiera acceda a ti + (del propietario) + Empezar + Enlace de grupo + inactivo + Invitación privada + Conecta con alguien + Firma del enlace verificada + Los miembros pueden chatear con los administradores + Migrar + Error de red + Los routers de la red no pueden saber\nquién se comunica con quién + Nuevo enlace de 1 solo uso + Sin cuenta. Sin teléfono. Sin email. Sin ID.\nEl cifrado más seguro. + Sin servidores activos + Enlace de un solo uso + Sólo los propietarios pueden modificar las preferencias de los canales. + En tu teléfono, no en el servidor. + ¿Abrir enlace externo? + O muestra el código QR en persona o por videollamada. + O usa el QR, imprímelo o muestralo en línea. + En propiedad: puedes poner en marcha tus propios servidores. + Privacidad: para propietarios y suscriptores. + Mensajería segura y privada. + El chat con los administradores no está permitido. + Canales públicos - habla con libertad 🚀 + Resultados del servidor: + Fiabilidad: muchos servidores por canal. + eliminado por el operador + Enlaces web seguros + Seguridad: los propietarios tienen la llave del canal. + Enviar una previsualización del enlace puede revelar tu dirección IP al sitio web. Puedes cambiarlo más tarde en los ajustes de privacidad. + Envía el enlace con cualquier mensajero, es seguro. El contacto debe pegarlo en SimpleX. + Configurar notificaciones + Configurar routers + Compartir canal… + Compartir mediante chat + ⚠️ Verificación de firma fallida: %s. + (firmado) + Los suscriptores pueden chatear con los administradores. + Para comunicarte + Pulsa para abrir + La conexión ha alcanzado al límite de mensajes no entregados + La primera red donde los grupos\ny los contactos son tuyos. + Para que la Red SimpleX perdure. + Usa esta dirección en el perfil de tus redes sociales, página web o firma email. + Esperando a que el propietario del canal añada servidores. + Hemos simplificado la conexión para los usuarios nuevos. + Por qué fue creado SimpleX. + Tu red + Tu perfil + Tu dirección pública + no están cifrados de extremo a extremo. Los servidores pueden ver estos mensajes.]]> + Compromisos en la red + Gobernanza no lucrativa + - aceptar el envío de vistas previas de los enlaces. \n- usar proxy SOCKS si está hablilitado.\n- prevenir el phishing mediante hipervínculos. \n- eliminar el seguimiento de los enlaces. + Menú inferior + Las previsualizaciones de enlaces se solicitan a través del proxy SOCKS. Las peticiónes DNS aún pueden usar el DNS local del sistema. + Menú superior diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 5483becb91..3f7d4ff025 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -730,7 +730,6 @@ تماس پذیرفته نامتمرکز پروفایل خود را ایجاد کنید - SimpleX چگونه کار می‌کند مخزن GitHub ما.]]> استفاده از چت بهترین گزینه برای باتری. شما اعلان‌ها را فقط وقتی دریافت می‌کنید که برنامه در حال اجراست (بدون سرویس پس‌زمینه).]]> @@ -2183,7 +2182,6 @@ شرایط به‌طور خودکار برای اپراتورهای فعال در: %s پذیرفته خواهد شد. سرورهای SMP پیکربندی‌شده سرورهای XFTP پیکربندی‌شده - پیکربندی اپراتورهای سرور سرورهای متصل شده اتصال نیاز به تجدید مذاکره رمزنگاری دارد. اتصالات @@ -2426,7 +2424,6 @@ این پیام حذف شده یا هنوز دریافت نشده است. زمان ناپدید شدن فقط برای مخاطبان جدید تنظیم شده است. برای مطلع شدن از نسخه‌های جدید، بررسی دوره‌ای برای نسخه‌های Stable یا Beta را فعال کنید. - تغییر لیست چت: برای برقراری تماس، اجازه دهید از میکروفن شما استفاده شود. تماس را پایان دهید و دوباره تلاش کنید. برای جلوگیری از جایگزینی لینک شما، می‌توانید کدهای امنیتی مخاطب را مقایسه کنید. برای دریافت diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index c6b094ab56..24634192ec 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -388,7 +388,6 @@ Miten Koko nimi: Miten markdownia käytetään - Miten SimpleX toimii Saapuva äänipuhelu Saapuva videopuhelu Sivuuta @@ -822,7 +821,7 @@ Arvioi sovellus Skannaa palvelimen QR-koodi Profiilipäivitys lähetetään kontakteillesi. - Jaa osoite kontakteille\? + Jaa osoite kontakteille? Lopeta jakaminen Lopeta osoitteen jakaminen\? Tallenna automaattisen hyväksynnän asetukset diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 42ba84ef8c..d95f8ad500 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -447,7 +447,6 @@ Créez votre profil Établir une connexion privée Comment ça fonctionne - Comment SimpleX fonctionne Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages. GitHub repository.]]> Batterie peu utilisée. L\'app vérifie les messages toutes les 10 minutes. Vous risquez de manquer des appels ou des messages urgents.]]> @@ -1105,7 +1104,7 @@ Vous pouvez accepter ou refuser les demandes de contacts. COULEURS DE L\'INTERFACE Vos contacts resteront connectés. - Partager l\'adresse avec vos contacts \? + Partager l\'adresse avec vos contacts ? Partager avec vos contacts Entrez un message de bienvenue… (facultatif) Cesser le partage @@ -2042,7 +2041,6 @@ %d sélectionné(s) Aperçu depuis la liste de conversation. Les messages seront marqués comme modérés pour tous les membres. - Afficher la liste des conversations : Vous pouvez choisir de le modifier dans les paramètres d\'apparence. Rétablir tous les conseils Mise à jour automatique de l\'app @@ -2370,7 +2368,6 @@ Permettre des fichiers et des médias seulement si votre contact les permet. Permettre à vos contacts d\'envoyer des fichiers et des médias. Tous les serveurs - Annuler seulement après que votre requête soit acceptée.]]> Vérifiez l\'adresse de relais et essayez à nouveau. Vérifiez le nom du relais et essayez à nouveau. @@ -2407,7 +2404,6 @@ Nom de relais prédéfini Serveurs prédéfinis Politique de confidentialité et conditions d\'utilisation. - Continuer Rejeter Rejeter la demande de contact rejeté diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml index 3084b8569b..84e806dda0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml @@ -90,7 +90,6 @@ Domaćin Kako utiče na bateriju Kako pomaže privatnosti - Kako SimpleX radi Grupni profil je uskladnjen na uredjajima korisnika, ne na serverima. O operatorima Skrivena šifra profila diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 310a52715a..2df64ae590 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -29,7 +29,7 @@ Elfogadja a kapcsolódási kérést? Elfogadás Elfogadás - Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. + Cím hozzáadása a profilhoz, hogy a SimpleX partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve a SimpleX partnerei számára. További kiemelőszín híváshiba Csoporttagok letiltása @@ -45,7 +45,7 @@ Az Android Keystore-t a jelmondat biztonságos tárolására használják – lehetővé teszi az értesítési szolgáltatás működését. Hibás az üzenet kivonata Háttér - Megjegyzés: az üzenet- és a fájlátjátszók SOCKS proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének küldése közvetlen kapcsolatot használ.]]> + Megjegyzés: az üzenet- és a fájlátjátszók SOCKS proxyn keresztül kapcsolódnak. A hívások pedig közvetlen kapcsolatot használnak.]]> Alkalmazásadatok biztonsági mentése Az adatbázis előkészítése sikertelen Az összes partnerével továbbra is kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára. @@ -91,7 +91,7 @@ Nem lehet meghívni a partnert! hibás az üzenet azonosítója Partneri kapcsolatkérések automatikus elfogadása - Megjegyzés: NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti.]]> + Megjegyzés: NEM fogja tudni helyreállítani vagy módosítani a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos szín Hozzáadás egy másik eszközhöz @@ -543,7 +543,7 @@ Kiszolgáló megadása kézzel A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! Hiba történt a csoporthivatkozás létrehozásakor - A galériából + Galéria Engedélyezés (csoport egyéni beállításainak megtartása) Hiba történt a partner törlésekor A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) @@ -624,7 +624,6 @@ Hiba történt a téma importálásakor Partner nevének és az üzenet tartalmának elrejtése Nem kompatibilis adatbázis-verzió - Hogyan működik a SimpleX Nem kompatibilis verzió Elrejtés Bejövő videóhívás @@ -674,7 +673,7 @@ Számítógépek A markdown használata Csevegési profil létrehozása - Védett a kéretlen tartalommal szemben + Védett a kéretlen tartalmakkal szemben Hordozható eszközök leválasztása Különböző nevek, profilképek és átvitelelkülönítés. Elutasítás esetén a kérés küldője NEM kap értesítést. @@ -944,7 +943,7 @@ Biztonsági kód beolvasása a partnere alkalmazásából. Lépjen kapcsolatba a csoport adminisztrátorával. Videó bekapcsolva - Profilnév: + Profil neve: Beillesztés Köszönjük, hogy telepítette a SimpleX Chatet! Csillagozás a GitHubon @@ -979,7 +978,7 @@ %s (jelenlegi) Saját SMP-kiszolgáló Véletlen - Megosztás a partnerekkel + Megosztás a SimpleX partnerekkel Ön Nincsenek csevegései Küldés @@ -995,7 +994,7 @@ Elküldve: %s Jelenlegi profil használata Ez az eszköz - Megosztja a címet a partnereivel? + Megosztja a címet a SimpleX partnereivel? Profiljelszó Téma Eltávolítja a jelmondatot a beállításokból? @@ -1038,7 +1037,7 @@ Kiszolgálók mentése Üdvözlőüzenet mp - A profilfrissítés el lesz küldve a partnerei számára. + A profilfrissítés el lesz küldve a SimpleX partnerei számára. Egyszerűsített inkognitómód Menti az üdvözlőüzenetet? Új csevegési profil létrehozásához indítsa újra az alkalmazást. @@ -1090,7 +1089,7 @@ Menti a beállításokat? Jelkód Ismeretlen hiba - Saját SMP-kiszolgálójának címe + Saját SMP-kiszolgáló címe Csevegési konzol megnyitása Eltávolítás Adatbázis-jelmondat beállítása @@ -1441,7 +1440,7 @@ A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni. Jelmondat mentése a Keystore-ba Köszönet a felhasználóknak a Weblate-en való közreműködésért! - Jelmondat mentése a beállításokban + Jelmondat mentése a beállításokba Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem lesznek elküldve. A második pipa, ami már nagyon hiányzott! ✅ Az átjátszó kiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát. @@ -1510,7 +1509,7 @@ Vagy QR-kód beolvasása Érvénytelen QR-kód Megtartás - Keresés vagy SimpleX-hivatkozás beillesztése + Keressen vagy adjon meg egy SimpleX-hivatkozást Belső hibák megjelenítése Kritikus hiba Belső hiba @@ -1812,7 +1811,7 @@ Fájlkiszolgáló-hiba: %1$s Fájl állapota Fájl állapota: %s - Másolási hiba + Hiba másolása Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén. Ellenőrizze, hogy a hordozható eszköz és a számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint a számítógép tűzfalában engedélyezve van-e a kapcsolat.\nMinden további problémát osszon meg a fejlesztőkkel. Nem lehet üzenetet küldeni @@ -1823,7 +1822,7 @@ Továbbított üzenet Az üzenet később is kézbesíthető, ha a tag aktívvá válik. Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja. - Hivatkozás beolvasása / beillesztése + Hivatkozás megadása vagy QR-kód beolvasása Konfigurált SMP-kiszolgálók Egyéb SMP-kiszolgálók Egyéb XFTP-kiszolgálók @@ -2002,7 +2001,6 @@ TCP-kapcsolat Mentheti az exportált archívumot. Tippek visszaállítása - Csevegési lista ki/be: Ezt a „Megjelenés” menüben módosíthatja. Új médiabeállítások Lejátszás a csevegési listából. @@ -2097,10 +2095,10 @@ Cím nyilvános megosztása SimpleX-cím megosztása a közösségi médiában. Egyszer használható meghívó megosztása egy baráttal - csak egyetlen partnerrel használható – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható.]]> + csak egyetlen partnerrel használható – személyesen vagy bármilyen üzenetváltó alkalmazáson keresztül megosztható.]]> Beállíthatja a partner nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást. Kapcsolatbiztonság - A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül. + A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó alkalmazáson keresztül. A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat a partnerével. A közösségi médiához Vagy a privát megosztáshoz @@ -2340,10 +2338,9 @@ A tagok összes üzenete meg fog jelenni! moderátorok Elfogadás - A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. + Ön kijelenti, hogy:\n- nyilvános csoportokban kizárólag megengedett tartalmakat oszt meg\n- tiszteletben tartja a többi felhasználót – nem küld senkinek kéretlen tartalmat Adatvédelmi szabályzat és felhasználási feltételek. - A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. - Kiszolgálóüzemeltetők beállítása + Az üzemeltetők kijelentik, hogy:\n- függetlenek maradnak\n- minimálisra csökkentik a metaadatok használatát\n- ellenőrzött, nyílt forráskódú szoftvereket futtatnak Nem támogatott kapcsolattartási hivatkozás Rövid hivatkozás Teljes hivatkozás @@ -2498,7 +2495,7 @@ Tiszta hivatkozás megnyitása Teljes hivatkozás megnyitása Nyomonkövetési paraméterek eltávolítása a hivatkozásokból - SimpleX-átjátszócím + SimpleX-átjátszó címe Hiba a csevegés olvasottként való megjelölésekor A célkiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s. A továbbító kiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s. @@ -2527,7 +2524,6 @@ Ha csatornákat hozott létre vagy csak csatlakozott hozzájuk, akkor azok véglegesen le fognak állni. aktív Közvetítés… - Mégse csatorna Csatorna Csatorna @@ -2537,9 +2533,9 @@ Kapcsolódás kapcsolódott kapcsolódás - Csatorna létrehozása - Csatorna létrehozása - Csatorna létrehozása (béta) + Nyilvános csatorna létrehozása + Nyilvános csatorna létrehozása + Nyilvános csatorna létrehozása (BÉTA) Csatorna létrehozása törölve sikertelen @@ -2597,9 +2593,9 @@ Hivatkozás dekódolása A teszt a(z) %s. lépésnél sikertelen volt. A kiszolgáló hitelesítést igényel az átjátszóhoz való kapcsolódáshoz, ellenőrizze a jelszavát. - Érvénytelen átjátszónév! + Érvénytelen az átjátszó neve! Ellenőrizze az átjátszó nevét, és próbálja újra. - Érvénytelen átjátszócím! + Érvénytelen az átjátszó címe! Ellenőrizze az átjátszó címét, és próbálja újra. Hiba az átjátszó hozzáadásakor Csevegési átjátszók @@ -2613,26 +2609,161 @@ %1$d/%2$d átjátszó kapcsolódva ÁTJÁTSZÓ Átjátszóhivatkozás - Átjátszócím + Átjátszó címe a következőn keresztül: %1$s Átjátszó címének megosztása - A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszócím ennek az átjátszónak a beállítására szolgált a csatornához. + A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához. Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához. Feliratkozó eltávolítása Az összes feliratkozó számára letiltja a feliratkozót? Hiba a csatorna létrehozásakor Visszavonja a csatorna létrehozását? Engedélyezzen legalább egy csevegési átjátszót a csatorna létrehozásához. - A saját profilja (%1$s) meg lesz osztva a csatorna átjátszóival és feliratkozóival. + A(z) %1$s nevű profilja meg lesz osztva a csatorna átjátszóival és feliratkozóival.\nAz átjátszók hozzáférhetnek a csatornaüzenetekhez. Átjátszók konfigurálása Nem sikerült kapcsolódni az átjátszóhoz Nem minden átjátszó kapcsolódott - Folytatás A csatorna %2$d átjátszóból %1$d használatával kezd el működni. Folytatja? - Átjátszócím + Átjátszó címe Ez egy csevegési átjátszó címe, nem használható kapcsolódásra. %1$s nevű csatornához!]]> Hiba a csatorna megnyitásakor Az összes feliratkozó számára feloldja a feliratkozó letiltását? Átjátszó tesztelése a nevének lekéréséhez.]]> + Csatorna teljes neve: + A csatornaprofil a feliratkozók eszközén és a csevegési átjátszókon van tárolva. + csatornaprofil frissítve + %d csatornaesemény + törölt csatorna + hiba: %s + Hiba a csatornaprofil mentésekor + Üzenethiba + Mentés és a csatorna feliratkozóinak értesítése + Csatornaprofil mentése + frissített csatornaprofil + Az alkalmazás %1$d sikertelen letöltési kísérlet után eltávolította ezt az üzenetet. + eltávolítva (%1$d kísérlet) + A csatorna ideiglenesen nem érhető el + A csatornának nincsenek aktív átjátszói. Próbáljon meg később csatlakozni. + nem lehet közvetíteni + az üzemeltető eltávolította + inaktív + Az összes átjátszó el lett távolítva + Nem sikerült kapcsolódni egyetlen átjátszóhoz sem + Nincsenek aktív átjátszók + %1$d átjátszó eltávolítva + %1$d átjátszóhoz nem sikerült kapcsolódni + %1$d átjátszó inaktív + %1$d/%2$d átjátszó aktív, %3$d eltávolítva + %1$d/%2$d átjátszó aktív, %3$d hiba + %1$d/%2$d átjátszó kapcsolódott, %3$d átjátszóhoz nem sikerült kapcsolódni + %1$d/%2$d átjátszó kapcsolódott, %3$d eltávolítva + Az átjátszók hozzáadása később lesz támogatott. + Várakozás a csatorna tulajdonosára az átjátszók hozzáadásához. + Üzleti cím + Csatornahivatkozás + Kapcsolattartási cím + Hiba a csatorna megosztásakor + (a tulajdonostól) + Csoporthivatkozás + Hivatkozás aláírása ellenőrizve. + Egyszer használható meghívó + Csatorna megosztása… + Megosztás egy csevegésen keresztül + ⚠️ Nem sikerült ellenőrizni az aláírást: %s. + (aláírva) + Koppintson ide a megnyitáshoz + Letiltás + Engedélyezés + Engedélyezi a hivatkozások előnézetét? + Hiba + Hálózati hiba + A hivatkozáselőnézet küldése felfedheti az Ön IP-címét a weboldal számára. Ezt később módosíthatja az adatvédelmi beállításokban. + A kapcsolat elérte a kézbesítetlen üzenetek korlátját + Átjátszóeredmények: + Csatornabeállítások + Csak a csatorna tulajdonosai módosíthatják a csatornabeállításokat. + Saját nyilvános cím + Új egyszer használható meghívó + Csatornák + Saját nyilvános cím létrehozása + Hivatkozás vagy QR-kód használata + Egy hivatkozás, ami egyetlen partnerrel való kapcsolat létrehozására szolgál + Saját hivatkozás létrehozása + Bárki számára, aki el szeretné érni Önt + Partner meghívása privátban + Hagyja, hogy valaki elérje Önt + Vagy mutassa meg a QR-kódot személyesen vagy videóhíváson keresztül. + Vagy használja ezt a QR-kódot – nyomtassa ki vagy mutassa meg online. + Küldje el a hivatkozást bármilyen üzenetváltó alkalmazáson keresztül – ez egy biztonságos módszer – és kérje meg a partnerét, hogy illessze be a SimpleX alkalmazásba. + Beszélgessen valakivel + Használja ezt a címet a közösségi oldalakon használt profiljaiban, weboldalakon vagy az e-mail aláírásában. + Könnyebben hívhatja meg a barátait 👋 + Nonprofit irányítás + - Hivatkozások előnézetének küldése.\n- SOCKS proxy használata, ha engedélyezve van.\n- Hiperhivatkozásokon keresztüli adathalászat megakadályozása.\n- Hivatkozások nyomonkövetési paramétereinek eltávolítása. + Tulajdonjog: saját átjátszókat üzemeltethet. + Adatvédelem: tulajdonosok és előfizetők számára. + Nyilvános csatornák – mondja el szabadon a véleményét 🚀 + Megbízhatóság: több átjátszó is használható csatornánként. + Biztonságos webhivatkozások + Biztonság: a csatornák kulcsait a tulajdonosok őrzik. + A SimpleX hálózat hosszú távú működésének biztosítása érdekében. + Az új felhasználók számára egyszerűbbé tettük a kapcsolatok létrehozását. + Fiók nélkül születtünk. + Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk. + Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot - nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött - telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód. + De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá. + Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide - Ön itt szuverén. + A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános. + A legrégebbi emberi szabadság - beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének - olyan infrastruktúrán alapul, amely nem tudja elárulni. + Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe. + Legyen szabad a saját hálózatában. + + Feliratkozók jelentései + A közvetlen üzenetek küldése a feliratkozók között engedélyezve van. + A közvetlen üzenetek küldése a feliratkozók között le van tiltva. + Legfeljebb az utolsó 100 üzenet elküldése az új feliratkozók számára. + Az előzmények ne legyenek elküldve az új feliratkozók számára. + A feliratkozók küldhetnek eltűnő üzeneteket. + A feliratkozók küldhetnek egymásnak közvetlen üzeneteket. + A feliratkozók közötti közvetlen üzenetek le vannak tiltva. + A feliratkozók véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + A feliratkozók reakciókat adhatnak hozzá az üzenetekhez. + A feliratkozók küldhetnek hangüzeneteket. + A feliratkozók küldhetnek fájlokat és médiatartalmakat. + A feliratkozók küldhetnek SimpleX-hivatkozásokat. + A feliratkozók jelenthetik az üzeneteket a moderátorok felé. + Legfeljebb az utolsó 100 üzenet lesz elküldve az új feliratkozók számára. + Az előzmények nem lesznek elküldve az új feliratkozók számára. + A csevegés az adminisztrátorokkal engedélyezve van a tagok számára. + A csevegés az adminisztrátorokkal engedélyezve van a feliratkozók számára. + Váljon szabaddá\na saját hálózatában. + A csevegés az adminisztrátorokkal le van tiltva. + A nyilvános csatornákban az adminisztrátorokkal való csevegések nem rendelkeznek végpontok közötti titkosítással – csak megbízható csevegési átjátszókkal használja őket. + A csevegés a tagokkal le van tiltva + Csevegés az adminisztrátorokkal + Engedélyezés + Engedélyezi a csevegést az adminisztrátorokkal? + Profil nevének megadása… + Vágjunk bele + A tagok cseveghetnek az adminisztrátorokkal + nem rendelkeznek végpontok közötti titkosítással. A csevegési átjátszók láthatják ezeket az üzeneteket.]]> + Átköltöztetés + Hálózati kötelezettségvállalások + A hálózati útválasztók nem tudhatják,\nhogy ki kivel beszélget + Nincs fiók. Nincs telefonszám. Nincs e-mail-cím. Nincs személyazonosító.\nA legbiztonságosabb titkosítás. + Az eszközön, nem pedig kiszolgálókon. + Megnyitja a külső hivatkozást? + Privát és biztonságos üzenetváltás. + A csevegés az adminisztrátorokkal le van tiltva. + Értesítések beállítása + Útválasztók beállítása + A feliratkozók cseveghetnek az adminisztrátorokkal + Miért jött létre a SimpleX? + Saját hálózat + Profil létrehozása + Az első hálózat, ahol Ön birtokolja\na saját kapcsolatait és csoportjait. + Alsó sáv + A hivatkozások előnézetét SOCKS proxyn keresztül kéri le a kliens. A DNS-lekérdezés viszont továbbra is történhet helyi szinten, a saját DNS-kiszolgálón keresztül. + Felső sáv diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mobile_3.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mobile_3.svg new file mode 100644 index 0000000000..e731314fcc --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mobile_3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mobile_4.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mobile_4.svg new file mode 100644 index 0000000000..4ed6a064bf --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_mobile_4.svg @@ -0,0 +1 @@ + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 2257d93efa..60ed7db384 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -619,7 +619,6 @@ Kesalahan saat menginisialisasi WebView. Pastikan Anda telah menginstal WebView dan arsitektur yang didukung adalah arm64.\nKesalahan: %s Gunakan obrolan Bagaimana caranya - Cara kerja SimpleX Berkala Panggilan suara masuk panggilan suara terenkripsi e2e @@ -2026,7 +2025,6 @@ Platform perpesanan dan aplikasi yang melindungi privasi dan keamanan Anda. Untuk melindungi privasi Anda, SimpleX gunakan ID terpisah untuk setiap kontak. PROXY SOCKS - Alihkan daftar obrolan: Tingkatkan dan buka obrolan Ketuk untuk gabung ke samaran Anda memblokir %s @@ -2345,7 +2343,6 @@ Frasa sandi di Keystore tidak dapat dibaca. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. Terima Dengan menggunakan SimpleX Chat, Anda setuju untuk:\n- hanya mengirim konten legal di grup publik.\n- hormati pengguna lain – tidak ada spam. - Konfigurasikan operator server Kebijakan privasi dan ketentuan penggunaan. Obrolan pribadi, grup, dan kontak Anda tidak dapat diakses oleh operator server. Frasa sandi di Keystore tidak dapat dibaca, silakan masukkan secara manual. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index b6b372da7c..adce58e804 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -270,7 +270,7 @@ Permetti ai tuoi contatti di inviare messaggi vocali. Database della chat eliminato ICONA APP - Ideale per la batteria. Riceverai notifiche solo quando l\'app è in esecuzione (NO servizio in secondo piano).]]> + Ideale per la batteria. Riceverai notifiche solo quando l\'app è in esecuzione (NESSUN servizio in secondo piano).]]> Consuma più batteria! L\'app funziona sempre in secondo piano: le notifiche vengono mostrate istantaneamente.]]> chiamata… annulla anteprima link @@ -299,7 +299,7 @@ Copiato negli appunti Crea link di invito una tantum Crea gruppo segreto - Scansiona codice QR.]]> + Scansiona un codice QR.]]> Dalla Galleria Immagine Video @@ -358,12 +358,11 @@ Videochiamata crittografata e2e terminata Come funziona - Come funziona SimpleX Rispondi alla chiamata Audio spento Audio acceso Chiamate audio e video - Auto-accetta le immagini + Accetta automaticamente le immagini hash del messaggio errato ID messaggio errato Chiamata terminata @@ -525,7 +524,7 @@ Autorizzazione negata! Rifiuta (scansiona o incolla dagli appunti) - Scansiona codice QR + Scansiona un codice QR Inizia una nuova conversazione Tocca il pulsante Grazie per aver installato SimpleX Chat! @@ -1105,11 +1104,11 @@ Indirizzo SimpleX COLORI DELL\'INTERFACCIA I tuoi contatti resteranno connessi. - Aggiungi l\'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L\'aggiornamento del profilo verrà inviato ai tuoi contatti. + Aggiungi l\'indirizzo al tuo profilo, in modo che i tuoi contatti di SimpleX possano condividerlo con altre persone. L\'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX. Crea un indirizzo per consentire alle persone di connettersi con te. Crea indirizzo SimpleX - Condividi con i contatti - Condividere l\'indirizzo con i contatti\? + Condividi con i contatti di SimpleX + Condividere l\'indirizzo con i contatti di SimpleX? Smetti di condividere Inserisci il messaggio di benvenuto… (facoltativo) Ciao! @@ -1144,7 +1143,7 @@ Tema scuro Se non potete incontrarvi di persona, mostra il codice QR in una videochiamata o condividi il link. Parliamo in SimpleX Chat - L\'aggiornamento del profilo verrà inviato ai tuoi contatti. + L\'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX. Guida per l\'utente.]]> Menu e avvisi Smettere di condividere l\'indirizzo\? @@ -1362,7 +1361,7 @@ Apri cartella del database La password verrà conservata nelle impostazioni come testo normale dopo averla cambiata o il riavvio dell\'app. La password viene conservata nelle impostazioni come testo normale. - Nota bene: i relay di messaggi e file sono connessi via proxy SOCKS. Le chiamate e l\'invio di anteprime dei link usano una connessione diretta.]]> + Nota bene: i relay di messaggi e file sono connessi via proxy SOCKS. Le chiamate usano una connessione diretta.]]> Cripta i file locali Crittografia di file e media memorizzati Nuova app desktop! @@ -1670,7 +1669,7 @@ Migrazione Migrazione completata Apri la schermata di migrazione - O incolla il link dell\'archivio + O incolla un link dell\'archivio O condividi in modo sicuro questo link del file Incolla link dell\'archivio Chiamate picture-in-picture @@ -1943,7 +1942,7 @@ Riconnetti tutti i server L\'indirizzo del server non è compatibile con le impostazioni di rete: %1$s. File - Scansiona / Incolla link + Incolla link / Scansiona Dimensione carattere Totale inviato Messaggio inoltrato @@ -2041,7 +2040,6 @@ Protegge il tuo indirizzo IP e le connessioni. Salva e riconnetti Ripristina tutti i suggerimenti - Cambia l\'elenco delle chat: Puoi cambiarlo nelle impostazioni dell\'aspetto. Riproduci dall\'elenco delle chat. Aumenta la dimensione dei caratteri. @@ -2207,7 +2205,7 @@ - Apri la chat sul primo messaggio non letto.\n- Salta ai messaggi citati. Condividi indirizzo pubblicamente Condividi l\'indirizzo SimpleX sui social media. - O importa file archivio + O importa un file dell\'archivio Telefoni remoti I messaggi diretti tra i membri sono vietati in questa chat. Dispositivi Xiaomi: attiva l\'avvio automatico nelle impostazioni di sistema per fare funzionare le notifiche.]]> @@ -2376,10 +2374,9 @@ Bloccare i membri per tutti? moderatori Tutti i nuovi messaggi di questi membri verranno nascosti! - Usando SimpleX Chat accetti di:\n- inviare solo contenuto legale nei gruppi pubblici.\n- rispettare gli altri utenti - niente spam. - Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. + Tu ti impegni a:\n- Pubblicare solo contenuto legale nei gruppi pubblici\n- Rispettare gli altri utenti. Niente spam + Gli operatori si impegnano a:\n- Essere indipendenti\n- Minimizzare l\'uso di metadati\n- Eseguire codice open source verificato Accetta - Configura gli operatori dei server Informativa sulla privacy e condizioni d\'uso. Questo link richiede una versione più recente dell\'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile. Link completo @@ -2570,7 +2567,6 @@ accettato attivo Bloccare l\'iscritto per tutti? - Annulla Annullare la creazione del canale? Prova il relay per recuperare il suo nome.]]> %1$s!]]> @@ -2583,21 +2579,21 @@ Il canale verrà eliminato per tutti gli iscritti, non è reversibile! Il canale verrà eliminato per te, non è reversibile! Il canale sarà operativo con %1$d di %2$d relay. Procedere? - Relay della chat - Relay della chat - Relay della chat - Relay della chat - I relay della chat inoltrano i messaggi nei canali che crei. - I relay della chat inoltrano i messaggi agli iscritti del canale. + Relay di chat + Relay di chat + Relay di chat + Relay di chat + I relay di chat inoltrano i messaggi nei canali che crei. + I relay di chat inoltrano i messaggi agli iscritti del canale. Controlla l\'indirizzo del relay e riprova. Controlla il nome del relay e riprova. Configura i relay Connetti connesso in connessione - Crea canale - Crea canale - Crea canale (BETA) + Crea canale pubblico + Crea canale pubblico + Crea canale pubblico (BETA) Creazione canale Decodifica il link Elimina canale @@ -2605,7 +2601,7 @@ eliminato Elimina relay Modifica profilo canale - Attiva almeno un relay della chat per creare un canale. + Attiva almeno un relay di chat per creare un canale. Inserisci il nome del relay… Errore di aggiunta del relay Errore di creazione del canale @@ -2628,7 +2624,6 @@ Proprietari Indirizzo relay preimpostato Nome relay preimpostato - Procedi relay RELAY Indirizzo del relay @@ -2661,7 +2656,7 @@ Ti sei connesso/a al canale attraverso questo link del relay. Il tuo canale Il tuo canale - Il tuo profilo %1$s verrà condiviso con i relay del canale e gli iscritti. + Il tuo profilo %1$s verrà condiviso con i relay del canale e gli iscritti.\nI relay hanno accesso ai messaggi del canale. L\'indirizzo del tuo relay Il nome del tuo relay Smetterai di ricevere messaggi da questo canale. La cronologia della chat sarà preservata. @@ -2671,4 +2666,140 @@ Tocca Iscriviti al canale Puoi condividere un link o un codice QR, chiunque sarà in grado di iscriversi al canale. Trasmetti + Nome completo del canale: + Il profilo del canale è memorizzato sui dispositivi degli iscritti e sui relay di chat. + profilo del canale aggiornato + %d eventi del canale + canale eliminato + scartato (%1$d tentativi) + errore: %s + Errore di salvataggio del profilo del canale + Errore del messaggio + Salva e avvisa gli iscritti del canale + Salva il profilo del canale + L\'app ha rimosso questo messaggio dopo %1$d tentativi di riceverlo. + profilo del canale aggiornato + %1$d/%2$d relay attivi, %3$d errori + %1$d/%2$d relay attivi, %3$d rimossi + %1$d/%2$d relay connessi, %3$d falliti + %1$d/%2$d relay connessi, %3$d rimossi + %1$d relay falliti + %1$d relay non attivi + %1$d relay rimossi + L\'aggiunta di relay verrà supportata prossimamente. + Tutti i relay falliti + Tutti i relay rimossi + impossibile trasmettere + Il canale non ha relay attivi. Prova a iscriverti più tardi. + Canale non disponibile temporaneamente + inattivo + Nessun relay attivo + rimosso da un operatore + In attesa che il proprietario del canale aggiunga dei relay. + Indirizzo di lavoro + Link del canale + Indirizzo di contatto + Errore nella condivisione del canale + (dal proprietario) + Link del gruppo + Firma del link verificata. + Condividi canale… + Condividi via chat + ⚠️ Verifica della firma fallita: %s. + (firmato) + Tocca per aprire + Link una tantum + Disattiva + Attiva + Attivare le anteprime dei link? + Errore + Errore di rete + Risultati relay: + L\'invio di un\'anteprima del link può rivelare il tuo indirizzo IP al sito. Puoi modificarlo nelle impostazioni di Privacy più tardi. + La connessione ha raggiunto il limite di messaggi non consegnati + Preferenze del canale + Solo i proprietari del canale possono modificarne le preferenze. + Un link per una persona da connettere + Canali + Connetti via link o codice QR + Crea il tuo link + Crea il tuo indirizzo pubblico + Per chiunque debba raggiungerti + Invita qualcuno in modo privato + Lascia che qualcuno si connetta a te + Nuovo link una tantum + O mostra il QR di persona o via videochiamata. + O usa questo QR: stampalo o mostralo online. + Invia il link tramite qualsiasi messenger, è sicuro. Chiedi di incollarlo in SimpleX. + Parla con qualcuno + Usa questo indirizzo nel tuo profilo di social media, sito web o firma email. + Il tuo indirizzo pubblico + È più facile invitare i tuoi amici 👋 + Organizzazione non a scopo di lucro + Per la sostenibilità della rete di SimpleX. + - scegli se inviare anteprime dei link.\n- usa il proxy SOCKS se attivato\n- previeni il phishing dei collegamenti ipertestuali.\n- rimuovi il tracciamento dei link. + Proprietà: puoi gestire i tuoi relay personali. + Privacy: per i proprietari e gli iscritti. + Canali pubblici - parla liberamente 🚀 + Affidabilità: relay multipli per canale. + Link web sicuri + Sicurezza: solo i proprietari hanno le chiavi del canale. + Abbiamo semplificato la connessione per i nuovi utenti. + Sei nato senza un account. + Nessuno monitorava le tue conversazioni. Nessuno disegnava una mappa delle tue posizioni. La privacy non era mai stata una caratteristica, era uno stile di vita. + Poi ci siamo trasferiti online e ogni piattaforma ha chiesto un pezzo di noi: il nome, il numero, gli amici. Abbiamo accettato che il prezzo da pagare per comunicare con gli altri fosse quello di far sapere a qualcuno con chi parliamo. Ogni generazione, sia di persone che di tecnologia, ha funzionato così: telefono, email, messenger, social media. Sembrava l\'unico modo possibile. + C\'è un\'altra via. Una rete senza numeri di telefono. Senza nomi utente. Senza account. Senza identificatori utente di alcun tipo. Una rete che connette le persone e trasferisce messaggi crittografati senza sapere chi è connesso. + Non una serratura migliore sulla porta di qualcun altro. Non un padrone di casa più gentile che rispetta la tua privacy, ma che continua a tenere traccia di tutti i visitatori. Non sei un ospite. Sei a casa tua. Nessun re può entrarvi: sei tu il sovrano. + Le tue conversazioni appartengono a te, come è sempre stato prima dell\'avvento di internet. La rete non è un luogo che visiti. È un luogo che crei e possiedi. E nessuno può portartelo via, che tu lo renda privato o pubblico. + La più antica libertà umana, parlare con un\'altra persona senza essere osservati, si basa su un\'infrastruttura che non può tradirla. + Perché abbiamo distrutto il potere di sapere chi sei. In modo che il tuo potere non possa mai esserti sottratto. + Vivi libero nella tua rete. + + Segnalazioni degli iscritti + Permetti l\'invio di messaggi diretti agli iscritti. + Proibisci l\'invio di messaggi diretti agli iscritti. + Invia fino a 100 ultimi messaggi ai nuovi iscritti. + Non inviare la cronologia ai nuovi iscritti. + Gli iscritti al canale possono inviare messaggi a tempo. + Gli iscritti al canale possono inviare messaggi diretti. + I messaggi diretti tra gli iscritti sono vietati. + Gli iscritti al canale possono eliminare irreversibilmente i messaggi inviati. (24 ore) + Gli iscritti al canale possono aggiungere reazioni ai messaggi. + Gli iscritti al canale possono inviare messaggi vocali. + Gli iscritti al canale possono inviare file e contenuti multimediali. + Gli iscritti al canale possono inviare link di Simplex. + Gli iscritti possono segnalare messaggi ai moderatori. + Vengono inviati ai nuovi iscritti fino a 100 ultimi messaggi. + La cronologia non viene inviata ai nuovi iscritti. + Consenti ai membri di chattare con gli amministratori. + Consenti agli iscritti di chattare con gli amministratori. + Vivi libero\nnella tua rete + Le chat con gli amministratori sono vietate. + Le chat con amministratori in canali pubblici non hanno crittografia E2E: usale solo con relay di chat fidati. + Le chat con i membri sono disattivate + Chat con amministratori + Attiva + Attivare le chat con gli amministratori? + Inserisci nome profilo… + I membri possono chattare con gli amministratori. + non sono crittografati end-to-end. I relay di chat possono vedere questi messaggi.]]> + Migra + Impegni sulla rete + Gli instradatori di rete non possono\nsapere chi parla con chi + Nessun account. Nessun telefono. Nessuna email. Nessun identificatore.\nLa crittografia più sicura. + Sul tuo telefono, non sui server. + Aprire il link esterno? + Messaggistica privata e sicura. + Vieta le chat con gli amministratori. + Configura le notifiche + Configura gli instradatori + Gli iscritti possono chattare con gli amministratori. + La prima rete in cui possiedi\ni tuoi contatti e i tuoi gruppi. + Perché costruiamo SimpleX. + La tua rete + Il tuo profilo + Cominciamo + Barra inferiore + L\'anteprima del link verrà richiesta via proxy SOCKS. La ricerca DNS può ancora accadere localmente tramite il tuo risolutore DNS. + Barra superiore diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index fb83b83735..faf69dfd03 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -482,7 +482,6 @@ הסתר פרופיל איך להשתמש במרקדאון איך זה עובד - איך SimpleX עובדת נתק עזרה הקבוצה תימחק עבור כל חברי הקבוצה – לא ניתן לבטל זאת! @@ -945,7 +944,7 @@ הצג קוד QR נעילת SimpleX כוכב ב־GitHub - לשתף כתובת עם אנשי קשר\? + לשתף כתובת עם אנשי קשר? עצור שיתוף הגדרת קוד גישה נעילת SimpleX @@ -2095,7 +2094,6 @@ לשליחה צ\'אט אחד עם חבר הודעה חדשה - הגדרת מפעילי שרת ניתן להגדיר שרתים דרך הגדרות. אישר אותך ממתין לסקירה diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 4c5b279ba7..5c17946c24 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -153,7 +153,6 @@ 通知の常時受信 SMPサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 WebRTC ICEサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 - SimpleX の仕様 通話中 電池消費がより高い!非アクティブ時でもバックグラウンドのサービスが常に稼働します(着信してすぐに通知が出ます)。]]> 発信中 @@ -1097,7 +1096,7 @@ システム認証の代わりに設定します。 プロフィールを非表示にできます! リレー サーバーは IP アドレスを保護しますが、通話時間は監視されます。 - アドレスを連絡先と共有しますか\? + アドレスを連絡先と共有しますか? 保留中の通話 データ移行の確認が正しくない %s を提供しました @@ -1826,7 +1825,6 @@ SMPサーバーの構成 接続中 XFTPサーバーの構成 - チャットリスト表示切り替え 連絡先 メッセージサーバ メディア&ファイルサーバ @@ -2014,7 +2012,6 @@ プライバシーとセキュリティの向上 承諾 プライバシーポリシーと利用条件 - サーバオペレータの設定 承諾 サーバオペレータは、プライベートチャット・グループ・連絡先にはアクセスできません。 SimpleX Chat を利用することで、以下の事項に同意したものと見なされます:\n- パブリックグループでは合法なコンテンツのみを送信すること。\n- 他のユーザを尊重すること、またスパムメッセージを送信しないこと。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 651d32518f..83f937db32 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -500,7 +500,6 @@ 설명서 내 서버 사용법 마크다운 사용법 - SimpleX 작동 방식 그룹 초대가 만료되었어요. 그룹 멤버는 보낸 메시지를 영구 삭제할 수 있습니다. (24 시간) 그룹 멤버는 음성 메시지를 보낼 수 있어요. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml index 0ea9328085..92985b15be 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml @@ -215,7 +215,6 @@ Bluetooth Profîla xwe çêke Çawa dişuxule - SimpleX çawa dişuxule Çawa tesîrê li pîlê dike Her serê pêlekê Di cih de diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 1e71459df9..bccd49eed9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -214,7 +214,6 @@ Ištrinti nuorodą Ištrinti nuorodą\? Klaida kuriant grupės nuorodą - Kaip SimpleX veikia Duomenų bazė šifruota! kūrėjas Ištrinti grupę\? @@ -527,8 +526,7 @@ Nutraukti adreso keitimą Automatiškai priimti kontaktų užklausas Jį įrašius visi duomenys bus pašalinti. - Kiekvienam kontaktui ir grupės nariui bus naudojamas atskiras TCP prisijungimas (ir SOCKS prisijungimo duomenys). -\nTurėkite omenyje: jei turite daug prisijungimų, akumuliatoriaus ir interneto duomenų sąnaudos gali būti žymiai didesnės ir, kartais, prisijungimai gali patirti nesėkmę. + Kiekvienam kontaktui ir grupės nariui bus naudojamas atskiras TCP prisijungimas (ir SOCKS prisijungimo duomenys). \nTurėkite omenyje: jei turite daug prisijungimų, akumuliatoriaus ir interneto duomenų sąnaudos gali būti žymiai didesnės ir, kartais, prisijungimai gali patirti nesėkmę.]]> %1$s nori su jumis susisiekti per Yra įjungtas akumuliatoriaus naudojimo optimizavimas, išjungiantis foninę tarnybą ir periodines užklausas apie naujas žinutes. Nustatymuose galite įjungti ją iš naujo. Visada įjungta diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml index 2843b2cf8d..c5473aeea4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml @@ -79,9 +79,9 @@ Nederīgs ziņojuma formāts Tiešraide Moderēts apraksts - E2ee Info E2ee - E2ee Info No Pq - E2ee Info Pq + + + Savienojuma lokālais displeja vārds Displeja vārds Savienojums izveidots Apraksts Jūs kopīgojāt vienreizējo saiti inkognito režīmā @@ -266,7 +266,7 @@ Pievienot ziņu Savienoties Vai sūtīt kontakta pieprasījumu? - Sūtot kontakta pieprasījumu, jūs atklāsiet savu SimpleX lietotājvārdu šim kontaktam. Vai vēlaties turpināt? + Sūtīt pieprasījumu bez ziņas Sūtīt pieprasījumu Nevar nosūtīt ziņu @@ -306,7 +306,7 @@ Galerijas attēla poga Galerijas video poga Paldies, ka instalējāt SimpleX - Jūs varat sazināties ar SimpleX čata dibinātāju + Lai sāktu jaunu čatu, palīdzības virsraksts Čata palīdzības pieskāriena poga Saglabāt neizmantoto uzaicinājuma jautājumu @@ -830,7 +830,7 @@ Pārbaudīt savienojumu Pārbaudīt kodu mobilajā ierīcē Šīs ierīces nosaukums - Šīs ierīces versija + Savienots mobilais tālrunis Savienots ar mobilo tālruni Ievadiet šīs ierīces nosaukumu @@ -843,17 +843,17 @@ Atvienot darbvirsmu Atvienot attālo resursdatoru Atvienot attālos resursdatorus - Attālais resursdators tika atvienots (paziņojums) + Attālais resursdators tika atvienots (virsraksts) Attālā vadība tika atvienota (virsraksts) - Attālais resursdators atvienots no + Attālā vadība atvienota ar iemeslu Attālās vadības savienojums apturēts (apraksts) Remote Ctrl savienojums ir pārtraukts Identity Desc Kopēšanas kļūda Vai atvienoties no darbvirsmas? Vienlaicīgi var darboties tikai viena ierīce - Atveriet mobilajā ierīcē un skenējiet QR kodu + Gaida, kad mobilā ierīce pieslēgsies Nepareiza darbvirsmas adrese Nesaderīga darbvirsmas versija @@ -867,7 +867,7 @@ Savienots ar darbvirsmu Savienota darbvirsma Pārbaudiet kodu ar darbvirsmu - Jauna darbvirsma + Saistītās darbvirsmas Datoru Ierīces Saistīto Datoru Iestatījumi @@ -885,10 +885,10 @@ Nejaušs Ports Atvērt Portu Ugunsmūrī Atvērt Portu Ugunsmūrī Apraksts - Attālā Saimniekdatora Kļūda - Trūkst - Attālā Saimniekdatora Kļūda - Nav Aktīvs - Attālā Saimniekdatora Kļūda - Aizņemts - Attālā Saimniekdatora Kļūda - Noildze + + + + Migrate To Device Imports Neizdevās Migrate To Device Atkārtot Importu Migrate To Device Ievadiet Paroli @@ -941,9 +941,9 @@ Tūlītējas paziņojumi Pakalpojumu paziņojumi Pakalpojumu paziņojumi atslēgti - Lai saglabātu privātumu, Simplex izmanto fona pakalpojumu, nevis uznirstošos paziņojumus, tas patērē mazāk datora akumulatora. - To var atslēgt caur iestatījumiem, paziņojumi joprojām tiek rādīti. - Izslēgt akumulatora optimizāciju + + + Izslēdzot pakalpojumu un periodiskos paziņojumus Periodiskie paziņojumi Periodiskie paziņojumi atslēgti @@ -952,15 +952,15 @@ Izslēgt sistēmas ierobežojumu Atslēgt paziņojumus Sistēmas ierobežots fons - Brīdinājums par sistēmas ierobežotu fonu + Sistēmas ierobežots fons zvanā Sistēmas ierobežots fons zvanā - Brīdinājums par sistēmas ierobežotu fonu zvanā + Ievadiet paroli Ievadiet paroli Datu bāzes inicializācijas kļūda Neizdevās inicializēt datu bāzi. - Xiaomi ignorēt akumulatora optimizāciju + Simplex pakalpojumu paziņojums Simplex pakalpojumu paziņojuma teksts Zvana pakalpojumu paziņojums audio zvanam @@ -1240,12 +1240,12 @@ Kamera nav pieejama Atļauja noraidīta Augstāk minētais, tad prievārds turpinājums - Pievienot kontaktu, lai izveidotu saiti vai savienotu, izmantojot saiti - Izveidot grupu, lai izveidotu jaunu grupu + + Lai savienotu, izmantojot saiti Ja esat saņēmis simplex ielūguma saiti, varat to atvērt pārlūkā - Datorā nolasīt QR kodu no lietotnes, izmantojot QR koda nolasīšanu - Mobilajā ierīcē noklikšķiniet uz atvērt mobilajā lietotnē, tad noklikšķiniet uz savienot lietotnē + + Pieņemt savienojuma pieprasījumu? Ja izvēlēsieties noraidīt, sūtītājs netiks informēts Pieņemt kontaktu @@ -1314,9 +1314,9 @@ Jūs tiksiet savienots, kad grupas saimnieka ierīce būs tiešsaistē Jūs tiksiet savienots, kad jūsu savienojuma pieprasījums tiks pieņemts Jūs tiksiet savienots, kad jūsu kontaktu ierīce būs tiešsaistē - Ja jūs nevarat tikties klātienē, rādiet QR videozvanā vai caur citu kanālu + Jūsu čata profils tiks nosūtīts jūsu kontaktam - Ja jūs nevarat tikties klātienē, skenējiet QR videozvanā vai lūdziet ielūguma saiti + Kopīgot ielūguma saiti Ielīmējiet saiti, ko saņēmāt, lai savienotos ar savu kontaktu Uzzināt vairāk @@ -1330,19 +1330,19 @@ Jūs varat kopīgot savu adresi Jūs nezaudēsiet savus kontaktus, ja izdzēsīsiet adresi Kopīgot vienreizēju saiti ar draugu - Vienreizēju saiti var izmantot tikai ar vienu kontaktu + Jūs varat iestatīt savienojuma nosaukumu, lai atcerētos Savienojuma drošība Simplex adrese un vienreizējās saites ir drošas kopīgošanai Lai pasargātu no jūsu saites aizvietošanas, salīdziniet kodus Jūs varat pieņemt vai noraidīt savienojumu - Lasiet vairāk lietotāja rokasgrāmatā ar saiti + Adrese vai vienreizēja saite Savienoties caur saiti Savienoties Ielīmēt Šī virkne nav savienojuma saite - Jūs varat arī savienoties, noklikšķinot uz saites + Jauna saruna Jauns Pievienot kontaktu cilni @@ -1373,13 +1373,13 @@ Tīkla sesijas režīms sesija Tīkla sesijas režīms serveris Tīkla sesijas režīms entitāte - Tīkla sesijas režīms lietotājs. + Tīkla sesijas režīms sesija. Tīkla sesijas režīms serveris. - Tīkla sesijas režīms entitāte. + Atjaunināt tīkla sesijas režīmu? - Atspējot sīpolu viesus, ja nav atbalsta - Socks proxy iestatījumu ierobežojumi + + Tīkla smp proxy režīms privātā maršrutēšana Tīkla smp proxy režīms vienmēr Tīkla smp proxy režīms nezināms @@ -1566,22 +1566,21 @@ Izveidot privātu savienojumu Migrēt no citas ierīces Kā tas darbojas - Kā darbojas SimpleX Lai aizsargātu privātumu, SimpleX izmanto ID rindām Tikai klientu ierīces glabā kontaktu grupas un e2e šifrētas ziņas - Visas ziņas un faili ir e2e šifrēti - Lasiet vairāk GitHub krātuvē. + + Izmantot čatu Ievada paziņojumu režīms Ievada paziņojumu režīma apakšvirsraksts Ievada paziņojumu režīms izslēgts Ievada paziņojumu režīms periodisks Ievada paziņojumu režīms pakalpojums - Ievada paziņojumu režīms izslēgts + Ievada paziņojumu režīms izslēgts, īss apraksts - Ievada paziņojumu režīms periodisks + Ievada paziņojumu režīms periodisks apraksts īsi - Ievada paziņojumu režīms pakalpojums + Ievada paziņojumu režīms pakalpojuma apraksts īsi Ievada paziņojumu režīms akumulators Iestatīt datu bāzes paroli @@ -1591,7 +1590,6 @@ Ievada nosacījumi, izmantojot jūs piekrītat Ievada nosacījumi privātuma politika un lietošanas noteikumi Ievada nosacījumi pieņemt - Ievada nosacījumi konfigurēt servera operatorus Ievada izvēlēties servera operatorus Ievada tīkla operatori Ievada tīkla operatori simplex flux vienošanās @@ -1752,7 +1750,7 @@ Atslēgu glabātuve tiek droši glabāta Iestatījumi tiek glabāti parastā tekstā Šifrēts ar nejaušu frāzi - Nav iespējams atgūt frāzi + Atslēgu glabātuve ļauj saņemt ntfs Frāze tiks saglabāta iestatījumos Jums katru reizi jāievada frāze @@ -1798,7 +1796,6 @@ Apstiprināt datu bāzes jauninājumus Vienas rokas saskarne Sarunu apakšējā josla - Vienas rokas saskarnes karte Vienas rokas saskarnes maiņas instrukcija Terminālis vienmēr redzams Sarunu saraksts vienmēr redzams @@ -1991,8 +1988,8 @@ Operatora nosacījumi pieņemti Operatora nosacījumi pieņemti aktivizētiem operatoriem Jūsu serveri - Operatoru nosacījumi pieņemti - Operatoru nosacījumi tiks pieņemti + + Operators Operatora serveri Operators @@ -2002,17 +1999,17 @@ Operatora izmantošanas slēdzis Izmantot operatora x serverus Operatora nosacījumi neizdevās ielādēt - Operatora nosacījumi pieņemti dažiem - Operatora tie paši nosacījumi tiks piemēroti - Operatora tie paši nosacījumi tiks piemēroti operatoriem - Operatora nosacījumi tiks piemēroti - Operatora nosacījumi tiks pieņemti dažiem - Operatoru nosacījumi arī tiks piemēroti + + + + + + Skatīt nosacījumus Pieņemt nosacījumus Operatora lietošanas nosacījumi Operatora atjaunotie nosacījumi - Operatora, lai izmantotu, pieņemiet nosacījumus + Operatora izmantošana ziņām Operatora izmantošana ziņu saņemšanai Operatora izmantošana ziņu privātai maršrutēšanai @@ -2327,9 +2324,9 @@ Atsauces Atsauces uz jums sarunās. Ziņojumi - Attālinātā hosta kļūda: slikts stāvoklis - Attālinātā hosta kļūda: slikta versija - Attālinātā hosta kļūda: atslēgts + + + Attālinātā kontrole: neaktīva Attālinātā kontrole: slikts stāvoklis Attālinātā kontrole: aizņemta @@ -2341,22 +2338,22 @@ Šī funkcija ir izstrādē. Savienojiet plānu, lai savienotos ar sevi Šis ir jūsu personīgais vienreizējais saite - Jūs jau savienojaties ar %1$s + %1$s]]> Jūs jau savienojaties Jūs jau savienojaties, izmantojot šo vienreizējo saiti Šis ir jūsu personīgais simplex adrese Atkārtot savienojuma pieprasījumu Jūs jau esat pieprasījis savienojumu, izmantojot šo adresi Pievienojieties savai grupai - Šis ir jūsu saite grupai %1$s + %1$s]]> Atkārtot pievienošanās pieprasījumu Grupa jau pastāv Čats jau pastāv - Jūs jau pievienojaties grupai %1$s + %1$s]]> Jūs jau pievienojaties grupai Jūs jau pievienojaties grupai, izmantojot šo saiti - Jūs jau esat grupā %1$s - Jūs jau esat savienots ar %1$s + %1$s]]> + %1$s]]> Savienojiet, izmantojot saiti Aģenta kritiska kļūda Notikusi kritiska kļūda aģentā. @@ -2401,19 +2398,19 @@ Migrēt no ierīces, pabeigt migrāciju Migrēt no ierīces, vai dzēst arhīvu? Migrēt no ierīces, augšupielādētais arhīvs tiks dzēsts - Migrēt no ierīces, izvēlieties migrēt no citas ierīces + Migrēt no ierīces, vai kopīgot šo faila saiti Migrēt no ierīces, dzēst datu bāzi no ierīces Migrēt no ierīces, sarunas uzsākšana vairākās ierīcēs nav atbalstīta Migrēt no ierīces, uzsākt sarunu Migrēt no ierīces, migrācija pabeigta - Migrēt no ierīces, nedrīkstat uzsākt datu bāzi divās ierīcēs - Migrēt no ierīces, izmantošana divās ierīcēs pārtrauc šifrēšanu + + Migrēt no ierīces, pārbaudīt datu bāzes paroli Migrēt no ierīces, pārbaudīt paroli Migrēt no ierīces, apstipriniet, ka atceraties paroli Migrēt no ierīces, pārbaudiet savienojumu un mēģiniet vēlreiz - Migrēt no ierīces, arhīvs tiks dzēsts + Kļūda, migrējot no ierīces, pārbaudot paroli Tīkla veids: nav tīkla savienojuma Tīkla veids: mobilais diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index d21b8b8f83..19aa92a4a0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -353,7 +353,6 @@ സ്വാഗത സന്ദേശം നൽകുക... (ഇച്ഛാനുസൃതമായ) സംരക്ഷിക്കാതെ പുറത്ത് പോവുക ഇത് എങ്ങനെ പ്രവർത്തിക്കുന്നു - SimpleX എങ്ങനെ പ്രവർത്തിക്കുന്നു കൃത്യസമയം പ്രവർത്തനരഹിതമാക്കുക എന്നതിൽ ഇല്ലാതാക്കി diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 655b1cedbd..cc81e5365b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -424,7 +424,6 @@ cursief Hoe het werkt gemiste oproep - Hoe SimpleX werkt Inkomende audio oproep Inkomend video gesprek Negeren @@ -1111,7 +1110,7 @@ Delen met contacten Uw contacten blijven verbonden. Profiel update wordt naar uw contacten verzonden. - Adres delen met contacten\? + Adres delen met contacten? Stop met delen Stop met het delen van adres\? Nodig vrienden uit @@ -2039,7 +2038,6 @@ Verwijder maximaal 20 berichten tegelijk. Sommige bestanden zijn niet geëxporteerd Alle hints resetten - Chat-lijst wisselen: U kunt dit wijzigen in de instellingen onder uiterlijk Creëren Vervagen voor betere privacy. @@ -2380,7 +2378,6 @@ Volledige link Niet-ondersteunde verbindingslink Korte link - Serveroperators configureren Privacybeleid en gebruiksvoorwaarden. Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. contact verwijderd diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 281a734ed3..9cc43851d6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -426,7 +426,6 @@ Możesz używać markdown do formatowania wiadomości: Utwórz swój profil Jak to działa - Jak SimpleX działa Natychmiastowy Jak wpływa na baterię Nawiąż prywatne połączenie @@ -1115,7 +1114,7 @@ Dowiedz się więcej Przestać udostępniać adres\? Automatycznie akceptuj - Udostępnić adres kontaktom\? + Udostępnić adres kontaktom? Wpisz wiadomość powitalną… (opcjonalne) Aktualizacja profilu zostanie wysłana do Twoich kontaktów. Zapisać ustawienia\? @@ -2008,7 +2007,6 @@ Połączenie TCP Nic nie jest zaznaczone Sprawdź czy link SimpleX jest poprawny. - Przełącz listę czatów: Archiwizuj kontakty aby porozmawiać później. Chroni Twój adres IP i połączenia. Osiągalny pasek narzędzi czatu @@ -2230,7 +2228,6 @@ Czat z administratorami Czat z członkiem Czatuj z członkami, zanim dołączą. - Konfigurowanie operatorów serwerów Połącz Połącz się szybciej! 🚀 kontakt usunięty @@ -2557,4 +2554,13 @@ Połączenie nie powiodło się niepowodzenie Jeśli dołączyłeś do kanałów lub je utworzyłeś, przestaną one działać na stałe. + Urodziłeś się bez konta. + Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie. + Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją. + Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony. + Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem. + Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne. + Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić. + Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana. + Ciesz się swobodą w swojej sieci. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index c0bbe4d6bb..c129d68521 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -87,8 +87,7 @@ Aceitar solicitações de contato automaticamente Aparência O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis. - Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. -\nAtenção: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar. + para cada contato e membro do grupo. \nAtenção: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar.]]> Bom para bateria. O aplicativo procura por mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]> chamda encerrada %1$s Converse com os desenvolvedores @@ -418,7 +417,6 @@ O arquivo será recebido quando seu contato estiver online, aguarde ou verifique mais tarde! O perfil do grupo é armazenado nos dispositivos dos membros, não nos servidores. ajuda - Como o SimpleX funciona Servidores ICE (um por linha) Ignorar A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde! @@ -1150,7 +1148,7 @@ Salvar configurações de aceitação automática Abrindo banco de dados… Alterar perfis de conversa - Compartilhar endereço com os contatos\? + Compartilhar endereço com os contatos? Seus contatos continuarão conectados. Todos os dados do aplicativo serão excluídos. A senha do aplicativo é substituída por uma senha de auto-destruição. @@ -1872,7 +1870,6 @@ Migrar para outro dispositivo Verificar palavra-passe WiFi - Alternar lista de conversa: Você pode mudar isso em configurações de Aparência. desativado nenhum @@ -2377,7 +2374,6 @@ Os servidores para novos arquivos do seu perfil de chat atual A conexão atingiu o limite de mensagens não entregues, seu contato pode estar offline. Mensagens não entregues - Configurar operadores de servidor Chats privados, grupos e seus contatos não são acessíveis aos operadores de servidor. Aceitar Ao usar o SimpleX Chat, você concorda em:\n- enviar apenas conteúdo legal em grupos públicos.\n- respeitar outros usuários – sem spam. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 2f19492237..81cf8ed452 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -525,8 +525,7 @@ Negru Blocați membrul pentru toți? Atât tu, cât și contactul tău puteți șterge definitiv mesajele trimise. (24 de ore) - Folosește mai multă baterie! -\nServiciul în fundal rulează mereu – notificările sunt afișate imediat ce mesajele sunt disponibile. + Folosește mai multă baterie! \nServiciul în fundal rulează mereu – notificările sunt afișate imediat ce mesajele sunt disponibile.]]> Nu se poate trimite mesajul Șterge Confirmați fișiere de la servere necunoscute. @@ -1505,7 +1504,6 @@ Ieșire fără salvare Parolă profil ascuns italic - Cum funcționează SimpleX Fără identificatori de utilizator. Acest lucru se poate întâmpla atunci când:\n1. Mesajele au expirat în aplicația de trimitere după 2 zile sau pe server după 30 de zile.\n2. Decriptarea mesajului a eșuat, deoarece tu sau contactul tău ați folosit un backup vechi al bazei de date.\n3. Conexiunea a fost compromisă. Eroare la exportarea bazei de date a conversației @@ -1892,7 +1890,6 @@ Video Mesaj vocal (%1$s) Trimite - Comută lista de conversații: Pentru a primi notificări, te rugăm să introduci parola bazei de date Se așteaptă videoclipul Mesaje nelivrate @@ -2284,7 +2281,6 @@ Ai partajat o cale de fișier nevalidă. Raportează problema dezvoltatorilor aplicației. Deschide în aplicația mobilă, apoi atinge Conectare în aplicație.]]> Actualizează adresa - Configurați operatorii serverului Condițiile vor fi acceptate pentru operatorii activați după 30 de zile. Apasă pe butonul de informații de lângă bara de adrese pentru a permite accesul la microfon. Colţ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 5445c57055..b5bbf47973 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -10,10 +10,10 @@ Вы соединитесь со всеми членами группы. Соединиться - соединено + соединен(а) ошибка соединяется - Установлено соединение с сервером, через который Вы получаете сообщения от этого контакта. + Вы подключены к серверу, используемому для приёма сообщений от этого соединения. Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта (ошибка: %1$s). Устанавливается соединение с сервером, через который Вы получаете сообщения от этого контакта. @@ -27,7 +27,7 @@ connection %1$d соединение установлено - приглашение соединиться + приглашение соединяется… Вы создали одноразовую ссылку Вы создали одноразовую ссылку инкогнито @@ -54,7 +54,7 @@ Превышено время соединения Ошибка соединения - Пожалуйста, проверьте Ваше соединение с сервером %1$s и попробуйте ещё раз. + Пожалуйста, проверьте Ваше соединение с %1$s и попробуйте ещё раз. Ошибка при отправке сообщения Ошибка при добавлении членов группы Ошибка при вступлении в группу @@ -78,22 +78,22 @@ Ошибка теста на шаге %s. Сервер требует авторизации для создания очередей, проверьте пароль. Хэш в адресе сервера не соответствует сертификату. - Соединение + Соединиться Создание очереди Защита очереди Удаление очереди - Разрыв соединения + Отключить Мгновенные уведомления Мгновенные уведомления! Мгновенные уведомления выключены! SimpleX выполняется в фоне вместо уведомлений через сервер.]]> - Он может быть выключен через Настройки – Вы продолжите получать уведомления о сообщениях пока приложение запущено.]]> + Он может быть выключен через Настройки - Вы продолжите получать уведомления о сообщениях пока приложение запущено.]]> Разрешите это в следующем окне, чтобы получать уведомления мгновенно.]]> Оптимизация батареи включена, поэтому сервис уведомлений выключен. Вы можете снова включить его через Настройки. Периодические уведомления Периодические уведомления выключены! - Приложение периодически получает новые сообщения — это потребляет несколько процентов батареи в день. Приложение не использует push уведомления — данные не отправляются с Вашего устройства на сервер. + Приложение периодически получает новые сообщения - это потребляет несколько процентов батареи в день. Приложение не использует push уведомления - данные не отправляются с Вашего устройства на сервер. Введите пароль Для получения уведомлений, пожалуйста, введите пароль от базы данных Ошибка базы данных @@ -114,7 +114,7 @@ Всегда включен Приложение может получить сообщение только тогда, когда запущено, в фоне сервис запускаться не будет Проверять новые сообщения раз в 10 минут на протяжении до 1 минуты - Фоновый сервис всегда включен. Уведомления будут показаны без задержки при наличии сообщений + Фоновый сервис всегда включен. Уведомления будут показаны без задержки при наличии сообщений. Текст сообщения Имена контактов Скрытое @@ -154,10 +154,10 @@ Редактировать Удалить Показать - Спрятать + Скрыть Разрешить Удалить сообщение? - Сообщение будет удалено – это действие нельзя отменить! + Сообщение будет удалено - это действие нельзя отменить! Сообщение будет помечено на удаление. Получатель(и) сможет(смогут) посмотреть это сообщение. Удалить для меня Для всех @@ -203,8 +203,8 @@ Файл Большой файл! - Ваш контакт отправил файл, размер которого превышает поддерживаемый в настоящее время максимальный размер (%1$s). - В настоящее время максимальный поддерживаемый размер файла составляет %1$s. + Ваш контакт отправил файл, размер которого превышает максимальный размер (%1$s). + Максимальный размер файла - %1$s. Ожидается приём файла Файл будет принят, когда Ваш контакт будет в сети, подождите или проверьте позже! Файл сохранён @@ -221,11 +221,11 @@ Контакт и все сообщения будут удалены - это действие нельзя отменить! Удалить контакт Имя контакта… - Соединение с сервером установлено + Соединено Соединение с сервером не установлено - Ошибка соединения с сервером - Ожидается соединение с сервером - Переключить адрес получения? + Ошибка + Ожидает + Поменять адрес получения? Адрес получения сообщений будет перемещён на другой сервер. Изменение адреса завершится после того как отправитель будет онлайн. Отправить сообщение @@ -248,7 +248,7 @@ Начать новый разговор Создать ссылку-приглашение Соединиться через ссылку или QR-код - Сканировать\nQR-код + Сканировать QR-код Создать секретную группу (чтобы отправить Вашему контакту) (сканировать или вставить из буфера) @@ -271,7 +271,7 @@ Сканировать QR-код.]]> Open in mobile app на веб странице, затем нажмите Соединиться в приложении.]]> - Принять запрос на соединение? + Принять запрос? Отправителю НЕ будет послано уведомление, если Вы отклоните запрос на соединение. Принять Принять инкогнито @@ -291,7 +291,7 @@ Без звука Уведомлять - Вы пригласили Ваш контакт + Вы пригласили контакт Вы приняли приглашение соединиться Удалить ожидаемое соединение? Контакт, которому Вы отправили эту ссылку, не сможет соединиться! @@ -311,7 +311,7 @@ удалить превью ссылки Настройки QR-код - SimpleX адрес + Адрес SimpleX помощь SimpleX команда SimpleX логотип @@ -320,14 +320,14 @@ Показать QR-код - Неверный QR-код + Ошибка QR-кода Этот QR-код не является ссылкой! Неверная ссылка! Эта ссылка не является ссылкой-приглашением! - Запрос на соединение послан! + Запрос на соединение отправлен! Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже! Соединение будет установлено, когда Ваш запрос будет принят. Пожалуйста, подождите или проверьте позже! - Соединение будет установлено, когда Ваш контакт будет в сети. Пожалуйста, подождите или проверьте позже. + Соединение будет установлено, когда Ваш контакт будет онлайн. Пожалуйста, подождите или проверьте позже! показать QR-код во время видеозвонка или поделиться ссылкой.]]> Ваш профиль будет отправлен \nВашему контакту @@ -341,8 +341,8 @@ Настройки Ваш адрес SimpleX - База данных - Подробнее о SimpleX Chat + Пароль и экспорт базы + Информация о SimpleX Chat Как использовать Форматирование сообщений Форматирование сообщений @@ -374,9 +374,9 @@ Поставить звёздочку на GitHub Внести свой вклад Оценить приложение - Использовать серверы, предосталенные SimpleX Chat? + Использовать серверы, предоставленные SimpleX Chat? Ваши SMP-серверы - Используются серверы предоставленные SimpleX Chat. + Используются серверы, предоставленные SimpleX Chat. Инфо Как использовать серверы Сохранённые WebRTC ICE-серверы будут удалены. @@ -388,7 +388,7 @@ Сохранить Сеть и серверы Настройки сети - Настройки сети + Дополнительные настройки Использовать SOCKS-прокси? Соединяться с серверами через SOCKS-прокси через порт %d? Прокси должен быть запущен до включения этой опции. Использовать прямое соединение с Интернет? @@ -406,7 +406,7 @@ Создать адрес Удалить адрес? Все контакты, которые соединились через этот адрес, сохранятся. - Поделиться\nссылкой + Поделиться ссылкой Удалить адрес Имя профиля: @@ -444,9 +444,9 @@ Эта строка не является ссылкой-приглашением! Открыть в приложении.]]> - звонок… + входящий звонок… пропущенный звонок - отклоненный звонок + отклонённый звонок принятый звонок звонок соединяется… активный звонок @@ -459,8 +459,8 @@ получен ответ… получено подтверждение… соединяется… - соединено - завершен + соединен(а) + завершён Будущее коммуникаций Более конфиденциальный @@ -473,8 +473,7 @@ Добавьте контакт Как это работает - Как SimpleX работает - Чтобы защитить Вашу конфиденциальность, SimpleX использует разные ID для всех ваших контактов. + Чтобы защитить Вашу конфиденциальность, SimpleX использует разные ID для каждого Вашего контакта. Только пользовательские устройства хранят контакты, группы и сообщения. GitHub репозитория.]]> @@ -491,22 +490,22 @@ e2e зашифрованный аудиозвонок Принять Отклонить - Закрыть - Звонок уже завершен! + Не отвечать + Звонок уже завершён! видеозвонок аудиозвонок Аудио и видеозвонки Ваши звонки - Всегда соединяться через relay + Всегда соединяться через релей Звонки на экране блокировки: - Принимать + Принять Показывать Выключить Ваши ICE-серверы WebRTC ICE-серверы - Relay-сервер защищает Ваш IP-адрес, но может отслеживать продолжительность звонка. - Relay-сервер используется только при необходимости. Другая сторона может видеть Ваш IP-адрес. + Релей-сервер защищает Ваш IP-адрес, но может отслеживать продолжительность звонка. + Релей-сервер используется только при необходимости. Другая сторона может видеть Ваш IP-адрес. Откройте SimpleX Chat\nчтобы принять звонок Вы можете разрешить принимать звонки на экране блокировки через Настройки. @@ -517,7 +516,7 @@ у контакта есть e2e шифрование у контакта нет e2e шифрования peer-to-peer - через relay сервер + через релей-сервер Закончить звонок Выключить видео Включить видео @@ -532,7 +531,7 @@ Отклоненный звонок Соединяющийся звонок Текущий звонок - Звонок завершен + Звонок завершён Принять звонок %1$d пропущенных сообщений @@ -540,10 +539,7 @@ ошибка ID сообщения повторное сообщение Пропущенные сообщения - Это может произойти, когда: -\n1. Клиент отправителя удалил неотправленные сообщения через 2 дня, или сервер – через 30 дней. -\n2. Расшифровка сообщения была невозможна, когда Вы или Ваш контакт использовали старую копию базы данных. -\n3. Соединение компроментировано. + Это может произойти, когда:\n1. Клиент отправителя удалил неотправленные сообщения через 2 дня, или сервер – через 30 дней.\n2. Расшифровка сообщения была невозможна, когда Вы или Ваш контакт использовали старую копию базы данных.\n3. Соединение компроментировано. Конфиденциальность Конфиденциальность @@ -580,7 +576,7 @@ Удалить данные чата Ошибка при запуске чата Остановить чат? - Остановите чат, чтобы экспортировать или импортировать архив чата или удалить базу данных. Вы не сможете получать и отправлять сообщения, пока чат остановлен. + Остановите чат, чтобы экспортировать или импортировать архив чата или удалить данные чата. Вы не сможете получать и отправлять сообщения, пока чат остановлен. Остановить Установите пароль База данных зашифрована случайным паролем. Пожалуйста, поменяйте его перед экспортом. @@ -588,21 +584,21 @@ Ошибка при экспорте архива чата Импортировать архив чата? Текущие данные Вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными. -\nЭто действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. +\nЭто действие нельзя отменить - ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. Импортировать Ошибка при удалении данных чата Ошибка при импорте архива чата Архив чата импортирован Перезапустите приложение, чтобы использовать импортированные данные чата. Удалить профиль? - Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. + Это действие нельзя отменить - Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. Данные чата удалены Перезапустите приложение, чтобы создать новый профиль. Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе Вы можете перестать получать сообщения от некоторых контактов. Удалить файлы во всех профилях чата Удалить все файлы Удалить файлы и медиа? - Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении. + Это действие нельзя отменить - все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении. Нет полученных или отправленных файлов %d файл(ов) общим размером %s никогда @@ -612,7 +608,7 @@ %s секунд Удалять сообщения через Включить автоматическое удаление сообщений? - Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут. + Это действие нельзя отменить - все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут. Удалить сообщения Ошибка при изменении настройки @@ -623,20 +619,20 @@ Уведомления будут работать только до остановки приложения! Удалить Зашифровать - Поменять + Обновить Текущий пароль… Новый пароль… Подтвердите новый пароль… - Сменить пароль + Поменять пароль Пожалуйста, введите правильный пароль. База данных НЕ зашифрована. Установите пароль, чтобы защитить Ваши данные. Android Keystore используется для безопасного хранения пароля - это позволяет стабильно получать уведомления в фоновом режиме. База данных зашифрована случайным паролем, Вы можете его поменять. Внимание: Вы не сможете восстановить или поменять пароль, если потеряете его.]]> Пароль базы данных будет безопасно сохранён в Android Keystore после запуска чата или изменения пароля - это позволит стабильно получать уведомления. - Пароль не сохранён на устройстве — Вы будете должны ввести его при каждом запуске чата. + Пароль не сохранён на устройстве - Вы будете должны ввести его при каждом запуске чата. Зашифровать базу данных? - Сменить пароль базы данных? + Поменять пароль базы данных? База данных будет зашифрована. База данных будет зашифрована и пароль сохранён в Keystore. Пароль базы данных будет изменён и сохранён в Keystore. @@ -660,7 +656,7 @@ Введите пароль… Сохранить пароль и открыть чат Открыть чат - Попытка изменить пароль базы данных не была завершена. + Попытка поменять пароль базы данных не была завершена. Восстановить резервную копию Восстановить резервную копию? Введите предыдущий пароль после восстановления резервной копии. Это действие нельзя отменить. @@ -680,7 +676,7 @@ Вступление в группу Вы вступили в группу. Устанавливается соединение с пригласившим Вас членом группы. Выйти - Выйти из группы + Выйти из группы? Вы перестанете получать сообщения от этой группы. История чата будет сохранена. Пригласить в группу Группа неактивна @@ -689,7 +685,7 @@ Группа не найдена! Эта группа больше не существует. Нельзя пригласить контакты! - Вы используете профиль инкогнито в этой группе. Для защиты Вашего основного профиля приглашать контакты запрещено. + Вы используете профиль инкогнито в этой группе. Для защиты Вашего основного профиля приглашать контакты запрещено Вы отправили приглашение в группу Вы приглашены в группу @@ -727,7 +723,7 @@ владелец удален(а) - покинул(а) + покинул(а) группу группа удалена приглашен(а) соединяется (представлен(а)) @@ -740,7 +736,7 @@ соединяется Нет контактов для добавления - Роль нового члена группы + Роль члена группы Развернуть выбор роли Пригласить в группу Не приглашать членов @@ -750,7 +746,7 @@ Выбрано контактов: %d Контакты не выбраны Нельзя пригласить контакт! - Вы пытаетесь пригласить контакт, который знает Ваш профиль инкогнито, в группу, где Вы используете основной профиль. + Вы пытаетесь пригласить контакт, который знает Ваш профиль инкогнито, в группу, где Вы используете основной профиль Пригласить в группу %1$s ЧЛЕНОВ ГРУППЫ @@ -765,8 +761,8 @@ Создать ссылку Удалить ссылку? Удалить ссылку - Вы можете поделиться ссылкой или QR-кодом — любой сможет присоединиться к группе. Члены группы останутся, даже если вы позже удалите ссылку. - Все члены группы, которые соединились через эту ссылку, останутся в группе. + Вы можете поделиться ссылкой или QR-кодом - любой сможет присоединиться к группе. Члены группы останутся, даже если вы позже удалите ссылку. + Все члены группы останутся соединены. Ошибка при создании ссылки группы Ошибка при удалении ссылки группы Только владельцы группы могут изменять предпочтения группы. @@ -777,7 +773,7 @@ Удалить члена группы Отправить сообщение - Член группы будет удалён - это действие нельзя отменить. + Член группы будет удалён - это действие нельзя отменить! Удалить ЧЛЕН ГРУППЫ Роль @@ -798,7 +794,7 @@ Получение через Отправка через Состояние сети - Переключить адрес получения + Поменять адрес получения Создать скрытую группу Группа полностью децентрализована – она видна только членам. @@ -818,12 +814,12 @@ Включить TCP keep-alive Сохранить Обновить настройки сети? - Обновление настроек приведёт к переподключению клиента ко всем серверам. + Обновление настроек приведет к сбросу и установке нового соединения со всеми серверами. Обновить Инкогнито Случайный профиль - Режим Инкогнито защищает Вашу конфиденциальность — для каждого контакта создаётся новый случайный профиль. + Режим Инкогнито защищает Вашу конфиденциальность - для каждого контакта создаётся новый случайный профиль. Это позволяет иметь много анонимных соединений без общих данных между ними в одном профиле пользователя. Когда Вы соединены с контактом инкогнито, тот же самый профиль инкогнито будет использоваться для групп с этим контактом. @@ -847,41 +843,41 @@ Предпочтения контакта Предпочтения группы Предпочтения группы - Настройки чатов + Ваши предпочтения Прямые сообщения Удаление для всех Голосовые сообщения включено включено для Вас включено для контакта - выключено + нет получено, не разрешено Разрешить Вашим контактам необратимо удалять отправленные сообщения. (24 часа) Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа) Контакты могут помечать сообщения для удаления; Вы сможете просмотреть их. Разрешить Вашим контактам отправлять голосовые сообщения. Разрешить голосовые сообщения, только если их разрешает Ваш контакт. - Запретить отправлять голосовые сообщений. + Запретить отправлять голосовые сообщения. Вы и Ваш контакт можете необратимо удалять отправленные сообщения. (24 часа) Только Вы можете необратимо удалять сообщения (Ваш контакт может помечать их на удаление). (24 часа) Только Ваш контакт может необратимо удалять сообщения (Вы можете помечать их на удаление). (24 часа) - Необратимое удаление сообщений запрещено в этой группе. + Необратимое удаление сообщений запрещено. Вы и Ваш контакт можете отправлять голосовые сообщения. Только Вы можете отправлять голосовые сообщения. Только Ваш контакт может отправлять голосовые сообщения. Голосовые сообщения запрещены в этом чате. - Разрешить посылать прямые сообщения членам группы. - Запретить посылать прямые сообщения членам группы. + Разрешить личные сообщения членам группы. + Запретить посылать личные сообщения членам группы. Разрешить необратимо удалять отправленные сообщения. (24 часа) Запретить необратимое удаление сообщений. Разрешить отправлять голосовые сообщения. Запретить отправлять голосовые сообщения. - Члены могут посылать прямые сообщения. + Члены могут посылать личные сообщения. Прямые сообщения между членами группы запрещены. Члены могут необратимо удалять отправленные сообщения. (24 часа) Необратимое удаление сообщений запрещено. - Участники могут отправлять голосовые сообщения. - Голосовые сообщения запрещены. + Члены группы могут отправлять голосовые сообщения. + Голосовые сообщения запрещены в этой группе. Минимальный расход батареи. Вы получите уведомления только когда приложение запущено, без фонового сервиса.]]> Уведомления Когда приложение запущено @@ -899,15 +895,15 @@ %d сек %dс %d мин - %d мес. - %d мес. + %d мес + %d мес %dм %dмес %d час - %d ч. + %d ч %dч %d день - %d нед. + %d нед Исчезающие сообщения Показать код безопасности Подтвердить код безопасности @@ -922,22 +918,22 @@ Не удалось открыть чаты Неправильный код безопасности! Сканировать код - Отправить живое сообщение — оно будет обновляться для получателей по мере того, как Вы его вводите + Отправить живое сообщение - оно будет обновляться для получателей по мере того, как Вы его вводите Создать ссылку группы Запретить отправлять исчезающие сообщения. Исчезающие сообщения запрещены. %dнед %dд - %d нед. + %d недель %d дней - Чтобы подтвердить безопасность end-to-end шифрования с Вашим контактом сравните (или сканируйте) код на ваших устройствах. + Чтобы подтвердить безопасность сквозного шифрования с Вашим контактом сравните (или сканируйте) код на ваших устройствах. %s подтверждён %s не подтверждён Код безопасности Подтвердить Сбросить подтверждение Разрешить исчезающие сообщения, только если Ваш контакт разрешает их Вам. - Запретить посылать исчезающие сообщения. + Запретить отправлять исчезающие сообщения. Члены могут посылать исчезающие сообщения. Что нового Новое в %s @@ -960,7 +956,7 @@ ошибка чата Принять Установить 1 день - неверные данные + ошибка данных Ссылки групп Админы могут создать ссылки для вступления в группу. Автоматически принимать запросы контактов @@ -981,23 +977,23 @@ Только локальные данные профиля Сообщения Серверы для новых соединений Вашего текущего профиля чата - Профили + Ваши профили чата Все чаты и сообщения будут удалены - это нельзя отменить! Сборка приложения: %s Версия приложения: v%s для каждого контакта и члена группы. \nОбратите внимание: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать.]]> для каждого профиля чата, который Вы имеете в приложении.]]> Версия ядра: v%s - Удалить профиль чата\? + Удалить профиль? Удалить профиль чата для Эта настройка применяется к сообщениям в Вашем текущем профиле чата - Отдельные сессии для + Отдельные транспортные сессии Обновить режим отдельных сессий\? - Имя профиля уже используется + Имя профиля уже используется! Ошибка создания профиля! У Вас уже есть профиль с таким именем. Пожалуйста, выберите другое имя. Ошибка выбора профиля! - По профилю чата или по соединению (БЕТА) + По профилю чата или по соединению (БЕТА). Благодаря пользователям – добавьте переводы через Weblate! Разные имена, аватары и транспортные сессии. Итальянский интерфейс @@ -1012,18 +1008,18 @@ Уменьшенное потребление батареи Отдельные транспортные сессии модерировано - модерировано %s + удалено %s Удалить сообщение члена группы\? Модерировать Сообщение будет удалено для всех членов группы. Сообщение будет помечено как удалённое для всех членов группы. Пожалуйста, свяжитесь с админом группы. - Вы \"читатель\" + только чтение сообщений только чтение сообщений читатель Роль при вступлении Ошибка обновления ссылки группы - Системный + Системная Аудио и видео звонки Ошибка при сохранении пароля пользователя Сохранить серверы\? @@ -1049,19 +1045,19 @@ Приветственное сообщение группы Скрыть профиль Без звука - Сделайте профиль конфиденциальным! + Сделайте профиль скрытым! Дополнительные улучшения скоро! - Теперь админы могут: \n- удалять сообщения членов группы. \n- приостанавливать членов группы (роль наблюдатель) + Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль наблюдатель) Защитите Ваши профили чата паролем! Раскрыть Поддержка bluetooth и другие улучшения. Сохранить приветственное сообщение\? - Установите приветственное сообщение для новых членов группы. - Нажмите на профиль, чтобы переключиться на него. - Благодаря пользователям - добавьте переводы через Weblate! + Установить сообщение для новых членов группы! + Нажмите на профиль, чтобы переключиться. + Благодаря пользователям – добавьте переводы через Weblate! Вы всё равно получите звонки и уведомления в профилях без звука, когда они активные. Вы можете скрыть или отключить уведомления профиля - нажмите и удерживайте профиль, чтобы открыть меню. - Изображение будет принято когда Ваш контакт его загрузит. + Изображение будет принято, когда Ваш контакт его загрузит. Файл будет принят когда Ваш контакт загрузит его. Обновление базы данных Подтвердить обновление базы данных @@ -1129,7 +1125,7 @@ Ошибка расшифровки Блокировка SimpleX не включена! Ошибка хэша сообщения - Хэш предыдущего сообщения отличается. + Хэш предыдущего сообщения отличается Подтвердить код Неправильный код Заблокировать через @@ -1231,8 +1227,8 @@ Запретить реакции на сообщения. секунд ЦВЕТА ИНТЕРФЕЙСА - Поделиться адресом с контактами\? - Обновлённый профиль будет отправлен Вашим контактам. + Поделиться адресом с контактами SimpleX? + Обновление профиля будет отправлено Вашим SimpleX контактам. Об адресе SimpleX Узнать больше Если Вы не можете встретиться лично, покажите QR-код во время видеозвонка или поделитесь ссылкой. @@ -1244,12 +1240,12 @@ Настроить тему Создайте адрес, чтобы можно было соединиться с Вами. Все Ваши контакты сохранятся. Обновлённый профиль будет отправлен Вашим контактам. - Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. + Добавьте адрес в свой профиль, чтобы Ваши SimpleX контакты могли поделиться им. Профиль будет отправлен Вашим SimpleX контактам. Создать адрес SimpleX - Поделиться с контактами + Поделиться с контактами SimpleX Прекратить делиться адресом\? Автоприём - Введите приветственное сообщение... (по желанию) + Введите приветственное сообщение... (опционально) Сохранить настройки\? Прекратить делиться Продолжить @@ -1311,13 +1307,13 @@ Во время импорта произошли некоторые ошибки: нет текста Поиск - Отключено + Выключено Они могут быть изменены в настройках контактов и групп. Отчёты о доставке выключены! шифрование работает для %s требуется новое соглашение о шифровании для %s Изменение адреса будет прекращено. Будет использоваться старый адрес. - Остановить изменение адреса + Прекратить изменение адреса Контакты Выключить (кроме исключений) шифрование согласовывается… @@ -1330,17 +1326,17 @@ Починить шифрование после восстановления из бэкапа. Починить Нет истории - Отправка отчётов о доставке включена для %d контактов. + Отправка отчётов о доставке включена для %d контактов Отправка отчётов о доставке будет включена для всех контактов во всех видимых профилях чата. Установка для Вашего активного профиля Установки для Вашего активного профиля - Отправка отчётов о доставке выключена для %d контактов. + Отправка отчётов о доставке выключена для %d контактов Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения! - Вторая галочка, когда сообщение доставлено! ✅ + Вторая галочка - знать, что доставлено! ✅ Вы можете включить их позже в настройках Конфиденциальности. Ошибка при прекращении изменения адреса Прекратить - Остановить изменение адреса? + Прекратить изменение адреса? Не избранный Нотификации перестанут работать, пока вы не перезапустите приложение Таймаут протокола на KB @@ -1365,11 +1361,11 @@ шифрование работает новое соглашение о шифровании разрешено код безопасности изменился - Отправлять отчёты о доставке + Отчёты о доставке %s в %s Починить соединение - Починка не поддерживается контактом. - Восстановление шифрования не поддерживается членом группы + Починка не поддерживается контактом + Починка не поддерживается членом группы Пересогласовать шифрование Быстрый поиск чатов Отчёты о доставке сообщений! @@ -1392,7 +1388,7 @@ Нет отфильтрованных разговоров Пересогласовать Пересогласовать шифрование\? - Запретить посылать файлы и медиа. + Запретить отправлять файлы и медиа. Соединиться Инкогнито Разрешить Открыть настройки приложения @@ -1416,7 +1412,7 @@ %s: %s В этой группе более %1$d членов, отчёты о доставке не отправляются. %s, %s и %d других членов соединены - выключено + выключен Эта функция ещё не поддерживается. Проверьте в следующем релизе. Соединиться напрямую\? Запрос на соединение будет отправлен этому члену группы. @@ -1435,7 +1431,7 @@ Расход батареи приложением / Без ограничений в настройках приложения.]]> База данных будет зашифрована, и пароль сохранён в настройках. Шифруйте сохранённые файлы и медиа - Обратите внимание: соединение с серверами файлов и сообщений устанавливаются через SOCKS-прокси. Звонки и картинки ссылок используют прямое соединение.]]> + Обратите внимание: соединение с серверами файлов и сообщений устанавливаются через SOCKS-прокси. Звонки используют прямое соединение.]]> Шифровать локальные файлы Приложение для компьютера! 6 новых языков интерфейса @@ -1459,12 +1455,12 @@ Пароль хранится в настройках, как открытый текст. Открыть Ошибка при создании контакта - Послать прямое сообщение контакту + Отправить личное сообщение контакту Ошибка отправки приглашения Отправьте сообщение чтобы соединиться запрос на соединение Раскрыть - Блокируйте членов группы + Заблокировать членов группы Повторить запрос на соединение? Ошибка нового соглашения о шифровании удалил(а) контакт @@ -1492,9 +1488,9 @@ Обнаружение по локальной сети и %d других событий Соединиться через ссылку? - Группы инкогнито + Инкогнито группы Вступление в группу уже начато! - %1$d сообщений отмодерировано членом %2$s + %1$d сообщений модерировано членом %2$s %s был отключен]]> Быстрое вступление и надёжная доставка сообщений. Соединиться с самим собой? @@ -1503,7 +1499,7 @@ Компьютер подключен Загрузка файла Подключение к компьютеру - Ошибка нового соглашения о шифровании + Ошибка нового соглашения о шифровании. Компьютеры Исправить имя на %s? Удалить %d сообщений? @@ -1538,7 +1534,7 @@ Неверный путь к файлу Сканируйте с мобильного Отключить компьютер? - Пожалуйста, подождите, пока файл загружается со связанного мобильного устройства. + Пожалуйста, подождите, пока файл загружается со связанного мобильного устройства Все новые сообщения от %s будут скрыты! Версия настольного приложения %s несовместима с этим приложением. заблокировано @@ -1595,15 +1591,15 @@ Поле поиска поддерживает ссылки-приглашения. История сообщений и улучшенный каталог групп. %s неактивен]]> - Превышено максимальное время соединения с компьютером. + Превышено максимальное время соединения с компьютером Компьютер отсоединён Неверный код приглашения у компьютера Загрузка чатов… Включить доступ к камере Нажмите, чтобы сканировать Создать группу: создать новую группу.]]> - Не отправлять историю новым членам группы. - Отправить до 100 последних сообщений новым членам группы. + Не отправлять историю новым членам. + Отправить до 100 последних сообщений новым членам. Все сообщения будут удалены - это нельзя отменить! Камера недоступна Код доступа в приложение @@ -1622,7 +1618,7 @@ Ошибка создания сообщения Ошибка удаления заметки Венгерский и Турецкий интерфейс - Поиск или вставить ссылку SimpleX + Искать или вставить ссылку SimpleX Этот QR-код не является SimpleX-ccылкой. С зашифрованными файлами и медиа. С уменьшенным потреблением батареи. @@ -1633,8 +1629,8 @@ Запустить чат? Личные заметки Доступ к истории - История не отправляется новым членам группы. - До 100 последних сообщений отправляются новым членам группы. + История не отправляется новым членам. + До 100 последних сообщений отправляются новым членам. Показывать внутренние ошибки Ошибка соединения с компьютером %s]]> @@ -1670,7 +1666,7 @@ установлен новый адрес контакта установлена новая картинка профиля профиль обновлён - Версия приложения на компьютере не поддерживается. Пожалуйста, установите одинаковую версию на оба устройства. + Версия приложения на компьютере не поддерживается. Пожалуйста, установите одинаковую версию на оба устройства Внутренняя ошибка Очистить личные заметки? Новый чат @@ -1687,21 +1683,21 @@ %s разблокирован Вы разблокировали %s Разблокировать для всех - Заблокировать члена группы для всех? + Заблокировать для всех? заблокирован - заблокировано админом - Заблокирован админом + заблокировано администратором + Заблокирован администратором Заблокировать для всех Ошибка при блокировании члена группы для всех - Разблокировать члена группы для всех? + Разблокировать члена для всех? Вы заблокировали %s - end-to-end шифрованием с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> - Чат защищён end-to-end шифрованием. - Чат защищён квантово-устойчивым end-to-end шифрованием. + сквозным шифрованием с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> + Чат защищён сквозным шифрованием. + Чат защищён квантово-устойчивым сквозным шифрованием. Открыть экран миграции Миграция с другого устройства Установить пароль - стандартное end-to-end шифрование + стандартное сквозное шифрование Приветственное сообщение слишком длинное Сообщение слишком большое Повторить загрузку @@ -1750,7 +1746,7 @@ Завершить миграцию Или передайте эту ссылку Миграция завершена - Внимание: запуск чата на нескольких устройствах не поддерживается и приведёт к сбоям доставки сообщений. + Внимание: запуск чата на нескольких устройствах не поддерживается и приведёт к сбоям доставки сообщений не должны использовать одну и ту же базу данных на двух устройствах.]]> Проверьте подключение к Интернету и повторите попытку Подтвердите, что Вы помните пароль базы данных для её миграции. @@ -1762,9 +1758,9 @@ Видеозвонок Чтобы продолжить, чат должен быть остановлен. Обратите внимание: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений.]]> - Внимание: архив будет удален.]]> + Внимание: архив будет удалён.]]> Подтвердите настройки сети - квантово-устойчивым end-to-end шифрованием с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> + квантово-устойчивым сквозным шифрованием с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> Мигрировать сюда Мигрировать на другое устройство Мигрируйте на другое устройство через QR-код. @@ -1774,7 +1770,7 @@ Квантово-устойчивое шифрование Используйте приложение во время звонка. Экспортированный файл не существует - Файл удален или ошибка ссылки + Файл удалён или ошибка ссылки Завершите миграцию на другом устройстве. Выполняется миграция базы данных. \nЭто может занять несколько минут. @@ -1815,7 +1811,7 @@ Ссылки SimpleX Разрешить отправлять ссылки SimpleX. Запретить отправку ссылок SimpleX - Участники могут отправлять ссылки SimpleX. + Члены группы могут отправлять ссылки SimpleX. админы все члены владельцы @@ -1825,7 +1821,7 @@ Включено для Переслать Переслать и сохранить сообщение - Ссылки SimpleX запрещены. + Ссылки SimpleX запрещены в этой группе. Переслать сообщение… Литовский интерфейс Источник сообщения остаётся конфиденциальным. @@ -1836,9 +1832,9 @@ ФАЙЛЫ Новые темы чатов нет - Светлый - Системный - Цвета темного режима + Светлая + Системная + Цвета тёмного режима Получайте файлы безопасно Конфиденциальная доставка 🚀 Улучшенная доставка сообщений @@ -1871,7 +1867,7 @@ Всегда Подтверждать файлы с неизвестных серверов. Всегда использовать конфиденциальную доставку. - Тёмный + Тёмная Отладка доставки Ошибка инициализации WebView. Обновите Вашу систему до новой версии. Свяжитесь с разработчиками. \nОшибка: %s @@ -1880,22 +1876,22 @@ Информация об очереди сообщений Персидский интерфейс Защитить IP-адрес - Защитите ваш IP-адрес от серверов сообщений, выбранных Вашими контактами. \nВключите в настройках Сеть и серверы. - Отправьте сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + Защитите ваш IP-адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сети и серверов*. + Отправлять сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. Конфиденциальная доставка Использовать конфиденциальную доставку с неизвестными серверами. Использовать конфиденциальную доставку с неизвестными серверами, когда IP-адрес не защищён. Когда IP защищён Да Чтобы защитить Ваш IP-адрес, приложение использует Ваши SMP-серверы для конфиденциальной доставки сообщений. - Изображения профилей + Картинки профилей Все режимы Тема приложения Сбросить на тему приложения Сбросить на тему пользователя Неизвестные серверы! Без Tor или VPN, Ваш IP-адрес будет доступен этим серверам файлов: \n%1$s. - Не использовать конфиденциальную маршрутизацию. + Не использовать конфиденциальную доставку. Никогда Неизвестные серверы Нет @@ -1924,7 +1920,7 @@ Применить к Не удаётся отправить сообщение Бета - Соединeно + Соединено попытки Готово Потвердить удаление контакта? @@ -1945,10 +1941,10 @@ Пересылающий сервер %1$s не смог подключиться к серверу назначения %2$s. Попробуйте позже. Версия пересылающего сервера несовместима с настройками сети: %1$s. Версия сервера назначения %1$s несовместима с пересылающим сервером %2$s. - Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удален. + Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удалён. Выбранные настройки чата запрещают это сообщение. Ошибка файла - Сканировать QR-код/ Вставить ссылку + Вставить ссылку / Сканировать Другие XFTP-серверы Настроенные XFTP-серверы Загрузка %s (%s) @@ -1964,7 +1960,7 @@ Всего Активные соединения Приём сообщений - В ожидании + Ожидает Загружено Статистика серверов будет сброшена - это нельзя отменить! Всего отправлено @@ -2002,7 +1998,7 @@ Подписок игнорировано Скопировать ошибку видеозвонок - Контакт будет удален — это нельзя отменить! + Контакт будет удалён - это нельзя отменить! Оставить разговор Удалить только разговор Удалить без уведомления @@ -2015,7 +2011,7 @@ Показать процент Слабое Среднее - Выключено + Нет Панель приложения внизу Текущий профиль Нет информации, попробуйте перезагрузить @@ -2045,8 +2041,8 @@ Выбрать Сообщения будут удалены для всех членов группы. Сообщения будут помечены как удалённые для всех членов группы. - Контакт удален! - Разговор удален! + Контакт удалён! + Разговор удалён! Член группы неактивен Прямого соединения пока нет, сообщение переслано или будет переслано админом. Ничего не выбрано @@ -2078,7 +2074,7 @@ Пригласить Статус сообщения Контакт соединяется, подождите или проверьте позже! - Контакт удален. + Контакт удалён. Попросите Вашего контакта разрешить звонки. Сохранить и переподключиться Отправьте сообщение, чтобы включить звонки. @@ -2089,14 +2085,14 @@ Ошибки Получено сообщений Сообщений отправлено - Переподключить все подключенные серверы для устранения неполадок доставки сообщений. Это использует дополнительный трафик. + Повторно подключите все серверы, чтобы принудительно доставить сообщения. Используется дополнительный трафик. Переподключить все серверы Переподключить сервер? Переподключить серверы? Сбросить всю статистику Статистика Вы не подключены к этим серверам. Для доставки сообщений на них используется конфиденциальная доставка. - Соединяйтесь с друзьями быстрее + Соединяйтесь с друзьями быстрее. Управляйте своей сетью Защищает ваш IP-адрес и соединения. Открыть настройки серверов @@ -2108,8 +2104,8 @@ Транспортные сессии Состояние соединения и серверов. Удаляйте до 20 сообщений за раз. - Загрузка обновления, не закрывайте приложение. - Файл не найден - скорее всего, файл был удален или отменен. + Загрузка обновления, не закрывайте приложение + Файл не найден - скорее всего, файл был удалён или отменен. Адрес пересылающего сервера несовместим с настройками сети: %1$s. написать Сообщение @@ -2126,13 +2122,12 @@ Новые медиа-опции Пригласить Новое сообщение - Переключите список чатов: Обновление приложения Загружать новые версии из GitHub. Увеличить размер шрифтов. Новый интерфейс 🎉 Открыть из списка чатов. - Сбросить все подсказки. + Сбросить все подсказки Вы можете изменить это в настройках Интерфейса. Пересылка %1$s сообщений Сохранение %1$s сообщений @@ -2155,11 +2150,11 @@ %1$s сообщений не переслано Переслать сообщения… Проверьте правильность ссылки SimpleX. - Неверная ссылка + Ошибка ссылки БАЗА ДАННЫХ - Ошибка инициализации WebView. Убедитесь, что у вас установлен WebView и его поддерживаемая архитектура – arm64.\nОшибка: %s + Ошибка инициализации WebView. Убедитесь, что у вас установлен WebView и его поддерживаемая архитектура - arm64.\nОшибка: %s Звук отключен - Сообщения будут удалены — это нельзя отменить! + Сообщения будут удалены - это нельзя отменить! Ошибка переключения профиля Выберите профиль чата Поделиться профилем @@ -2192,13 +2187,13 @@ Имя пользователя Ваши учётные данные могут быть отправлены в незашифрованном виде. Удалить архив? - Загруженный архив базы данных будет навсегда удален с серверов. + Загруженный архив базы данных будет навсегда удалён с серверов. Принятые условия Принять условия Нет серверов сообщений. Нет серверов для приёма сообщений. Ошибки в настройках серверов. - Для профиля %s: + Для профиля чата %s: Нет серверов файлов и медиа. Нет серверов для приёма файлов. Нет серверов для отправки файлов. @@ -2211,7 +2206,7 @@ Посмотреть условия Посмотреть условия %s.]]> - Условия будут автоматически приняты для включенных операторов: %s + Условия будут автоматически приняты для включенных операторов: %s. Условия приняты: %s. Вебсайт %s.]]> @@ -2251,8 +2246,8 @@ Адрес SimpleX или одноразовая ссылка? Настройки адреса Добавьте сотрудников в разговор. - Бизнес адрес - end-to-end шифрованием, с пост-квантовой безопасностью в прямых разговорах.]]> + Бизнес-адрес + сквозным шифрованием, с пост-квантовой безопасностью в прямых разговорах.]]> Приложение всегда выполняется в фоне Проверять сообщения каждые 10 минут Без фонового сервиса @@ -2272,11 +2267,11 @@ Удалить разговор Удалить разговор? Пригласить в разговор - Разговор будет удален для всех участников - это действие нельзя отменить! + Разговор будет удалён для всех участников - это действие нельзя отменить! Оператор %s серверы %s.]]> - Условия будут приняты: %s + Условия будут приняты: %s. Оператор сети Использовать %s Использовать серверы @@ -2284,15 +2279,15 @@ %s.]]> Или импортировать файл архива Доступная панель чата - Разговор будет удален для Вас - это действие нельзя отменить! + Разговор будет удалён для Вас - это действие нельзя отменить! Покинуть разговор Только владельцы разговора могут поменять предпочтения. Текст условий использования не может быть показан, вы можете посмотреть их через ссылку: Разговор - Участник будет удалён из разговора - это действие нельзя отменить. + Член будет удалён из разговора - это действие нельзя отменить! Серверы по умолчанию Роль будет изменена на %s. Все участники разговора получат уведомление. - Ваш профиль будет отправлен участникам разговора. + Ваш профиль будет отправлен участникам разговора %s.]]> %s.]]> Условия использования @@ -2316,15 +2311,15 @@ Нет серверов для доставки сообщений. Вы можете настроить серверы позже. SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение. - Приложение защищает вашу конфиденциальность, используя разные операторы в каждом разговоре. + Приложение улучшает конфиденциальность, используя разных операторов в каждом разговоре. Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем. Ошибка сохранения серверов Условия будут приняты для включенных операторов через 30 дней. Ошибка приёма условий Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности. - Например, если ваш контакт получает сообщения через сервер SimpleX Chat, ваше приложение будет доставлять их через сервер Flux. - Прямые сообщения между участниками запрещены в этом разговоре. + Например, если Ваш контакт получает сообщения через сервер SimpleX Chat, Ваше приложение доставит их через сервер Flux. + Прямые сообщения между членами группы запрещены. Группы Удалить Удалить список? @@ -2333,30 +2328,30 @@ Избранное запрошено соединение Редактировать - Предприятия + Бизнесы Включить журналы - О операторах + Об операторах Ошибка при сохранении базы данных Соединение не готово. Ошибка обновления списка чата Ошибка создания списка чатов Список Нет чатов в списке %s. - Без непрочитанных чатов - Никаких чатов + Нет непрочитанных чатов + Нет чатов Чаты не найдены Все чаты будут удалены из списка %s, а сам список удалён Добавить список - Примечания + Заметки Открыть в %s Создать список Добавить в список Изменить список Сохранить список Имя списка... - Исправить соединение? + Починить соединение? Соединение требует повторного согласования шифрования. - Исправление + Починить Выполняется повторное согласование шифрования. принятое приглашение Ошибка при загрузке списков чатов @@ -2365,25 +2360,25 @@ Пожаловаться Спам Пожаловаться на спам: увидят только модераторы группы. - Это действие не может быть отмененено - сообщения, отправленные и полученные в этом чате ранее чем выбранное, будут удалены - Получайте уведомления от упоминаний. + Это действие нельзя отменить - сообщения в этом чате, отправленные или полученные раньше чем выбрано, будут удалены. + Уведомления, когда Вас упомянули. Сообщения о нарушениях запрещены в этой группе. Пожаловаться на нарушение: увидят только модераторы группы. - Установить имя чата… + Имя чата… Улучшенная производительность групп - Приватные названия медиафайлов. + Конфиденциальные названия медиафайлов. Спам Сообщения о нарушениях Непрочитанные упоминания Да Упоминайте членов группы 👋 - Улучшенная приватность и безопасность + Улучшенная конфиденциальность и безопасность Ускорено удаление групп. Ускорена отправка сообщений. Помогайте админам модерировать их группы. Организуйте чаты в списки Вы можете сообщить о нарушениях - Установите время исчезания сообщений в чатах. + Установите срок хранения сообщений в чатах. Вы можете упомянуть до %1$s пользователей в одном сообщении! Причина сообщения? Эта жалоба будет архивирована для вас. @@ -2392,7 +2387,7 @@ Ошибка чтения пароля базы данных сообщение о нарушении заархивировано %s Нарушение правил группы - Неприемлемое сообщение + Неприемлемый контент Другая причина Неприемлемый профиль %d сообщений о нарушениях @@ -2419,7 +2414,7 @@ Не пропустите важные сообщения. Ошибка сохранения настроек заархивированное сообщение о нарушении - архивировать + Архивировать Архивировать сообщение о нарушении? Пароль не может быть прочитан из Keystore. Это могло произойти после обновления системы, несовместимого с приложением. Если это не так, обратитесь к разработчикам. Пароль не может быть прочитан из Keystore, пожалуйста, введите его. Это могло произойти после обновления системы, несовместимого с приложением. Если это не так, обратитесь к разработчикам. @@ -2435,7 +2430,7 @@ Пожаловаться на профиль: увидят только модераторы группы. Сообщения о нарушениях Пожаловаться: увидят только модераторы группы. - Выключить уведомления для всех + Все без звука Использовать TCP-порт %1$s, когда порт не указан. Использовать TCP-порт 443 только для серверов по умолчанию. Все серверы @@ -2460,18 +2455,17 @@ модераторы Удалить членов группы? Принять - Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей – не отправлять спам. - Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. - Настроить операторов серверов + Вы обязуетесь:\n- Только законный контент в публичных группах\n- Уважать других пользователей - без спама + Операторы обязуются:\n- Быть независимыми\n- Минимизировать использование метаданных\n- Использовать проверенный и открытый исходный код Политика конфиденциальности и условия использования. - всех + все Принять Член группы хочет присоединиться. Принять? группа удалена - удален из группы + удалён из группы %d чата(ов) контакт не готов - контакт удален + контакт удалён не синхронизирован запрос на вступление отклонён Новый член группы хочет присоединиться. @@ -2479,16 +2473,16 @@ ожидает одобрения Отклонить Отклонить члена группы? - Ошибка при удалении чата + Ошибка при удалении чата с членом группы Полная ссылка - Ошибка при вступлении члена группы + Ошибка вступления члена группы Ссылка не поддерживается Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку. %d сообщений Вы можете найти Ваши жалобы в Чате с админами. Чат с админами Чат с членом группы - выключено + нет Одобрять членов группы Чаты с членами группы Приём членов в группу @@ -2499,7 +2493,7 @@ Принять члена группы одобрен админами Жалоба отправлена модераторам - Вы вышли + Вы покинули группу нельзя отправлять %d чатов с членами группы контакт выключен @@ -2509,10 +2503,10 @@ Сохранить настройки вступления? Вы приняли этого члена группы рассмотрение - Установить вступление в группу + Приём членов в группу Удалить чат с членом группы? Удалить разговор - принял %1$s + принят %1$s Чат с админами Вы приняты 1 чат с членом группы @@ -2522,7 +2516,7 @@ Добавить сообщение О себе: Нельзя поменять профиль - end-to-end шифрованием.]]> + сквозным шифрованием.]]> только после того как Ваш запрос будет принят.]]> Чат с админами Общайтесь с членами группы до того как принять их. @@ -2548,8 +2542,8 @@ Таймаут конфиденциальной доставки Адрес будет коротким, и Ваш профиль будет добавлен в адрес. Фоновый таймаут протокола - Отклонить запрос на соединение - Может удалять сообщения и блокировать членов группы. + Отклонить запрос + Может удалять сообщения и блокировать членов. запрос отправлен Одобрять членов группы Отправить запрос на соединение? @@ -2560,7 +2554,7 @@ Обновить ссылку группы? Обновить Обновить адрес? - Цель: + Описание: Фоновый таймаут TCP-соединения Отправитель не будет уведомлён. Член группы удалён - невозможно принять запрос @@ -2572,7 +2566,7 @@ Использовать профиль инкогнито 4 новых языков интерфейса Принять запрос на соединение - Бизнес контакт + Бизнес-контакт Каталонский, Индонезийский, Румынский и Вьетнамский - благодаря нашим пользователям! Создайте Ваш адрес Описание слишком длинное @@ -2592,7 +2586,7 @@ Обновите Ваш адрес Обновить ссылку группы Приветствуйте Ваши контакты 👋 - Ваш бизнес контакт + Ваш бизнес-контакт Ваш контакт Ваша группа Разрешить файлы и медиа, только если их разрешает Ваш контакт. @@ -2604,7 +2598,7 @@ Только Ваш контакт может отправлять файлы и медиа. Откройте чтобы использовать бот Запретить отправлять файлы и медиа. - Нажмите Соединиться, чтобы использовать бот. + Нажмите Соединиться, чтобы использовать бот Вы должны быть соединены, чтобы отправлять команды. Удалённые настройки Открыть очищенную ссылку @@ -2613,15 +2607,15 @@ Ошибка прочтения чата Хэш в адресе пересылающего сервера не соответствует сертификату: %1$s. Хэш в адресе сервера не соответствует сертификату: %1$s. - Ссылка SimpleX relay + Адрес релея SimpleX Хэш в адресе сервера назначения не соответствует сертификату: %1$s. - Удалить сообщения участника - Удалить сообщения участника? + Удалить сообщения члена группы + Удалить сообщения члена группы? Удалить сообщения - Сообщения участника будут удалены - это действие не обратимо! + Сообщения члена группы будут удалены - это нельзя отменить! нет подписки - Вы не подключенны к серверу через который Вы получали сообщения от этого контакта (без подписки). - Удалить члена группы и удалить сообщения + Вы не подключены к серверу, через который Вы получали сообщения от этого контакта (нет подписки). + Удалить вместе с сообщениями Все сообщения Файлы Фильтр @@ -2634,4 +2628,250 @@ Поиск голосовых сообщений Видео Голосовые сообщения + %1$d подписчик + %1$d подписчиков + Отменить создание канала? + Это ваша ссылка на канал %1$s! + канал + Канал + Канал + Полное имя канала: + Ссылка канала + Участники канала + Имя канала + профиль канала обновлён + Канал будет удалён для всех подписчиков - это нельзя отменить! + Канал будет удалён для Вас - это нельзя отменить! + Настроить релеи + Соединиться + соединен(а) + соединяется + Создать публичный канал + Создать публичный канал + Создать публичный канал (БЕТА) + Создание канала + %d событий канала + Удалить канал + Удалить канал? + Удалить релей + Редактировать профиль канала + Введите имя релея… + Ошибка добавления релея + Ошибка при создании канала + Ошибка при открытии канала + ошибка: %s + Ошибка при сохранении профиля канала + Неверный адрес релея! + Неверное имя релея! + приглашен(а) + Войти в канал + Покинуть канал + Выйти из канала? + Ссылка + %1$d/%2$d релеев активны + %1$d/%2$d релеев подключены + %1$d/%2$d релеев подключены, %3$d с ошибками + принят(а) + активный + Заблокировать подписчика для всех? + Опубликовать + Канал начнёт работу с %1$d из %2$d релеев. Продолжить? + Чат-релей + Чат-релеи + Чат-релеи + Чат-релеи + Чат-релеи пересылают сообщения в Ваших каналах. + Чат-релеи пересылают сообщения подписчикам каналов. + Проверьте адрес релея и попробуйте снова. + Проверьте имя релея и попробуйте снова. + удалено + Предпочтения канала + Ссылка канала + Профиль канала хранится на устройствах подписчиков и на чат-релеях. + Канал временно недоступен + Выключить + Включить + Ошибка + Бизнес-адрес + нельзя публиковать + Включите хотя бы один релей чатов для создания канала. + Новый чат-релей + Нет чат-релеев + Чат-релеи не включены. + Это адрес чат-релея, с ним нельзя соединиться. + %1$d/%2$d релеев активны, %3$d с ошибками + %1$d/%2$d релеев активны, %3$d с ошибками + %1$d/%2$d релеев активны, %3$d удалены + %1$d/%2$d релеев подключены, %3$d с ошибками + %1$d/%2$d релеев подключены, %3$d удалены + %1$d релеев не работает + %1$d релеев неактивно + %1$d релеев удалено + Нет активных релеев + У канала нет активных релеев. Попробуйте подключиться позже. + Включить картинки ссылок? + Каналы + удалил(а) канал + Адрес контакта + Все релеи удалены + неактивен + Подпись ссылки проверена. + Открыть канал + Открыть новый канал + Владельцы + ВЛАДЕЛЕЦ + релей + РЕЛЕЙ + Адрес релея + Адрес релея + Ошибка подключения релея + Ссылка релея + Результаты релея: + удалено оператором + Удалить подписчика + Удалить подписчика? + Сохранить и уведомить подписчиков канала + Сохранить профиль канала + Отправка картинки ссылки может раскрыть Ваш IP-адрес веб-сайту. Вы можете изменить это в настройках безопасности позже. + Отправьте ссылку через любой мессенджер - это безопасно. Попросите вставить её в SimpleX. + Для подключения к релею требуется авторизация, проверьте пароль. + Предупреждение сервера + Поделиться каналом… + Поделиться адресом релея + Поделиться в чате + ⚠️ Ошибка проверки подписи: %s. + ПОДПИСЧИК + Подписчики + Подписчик будет удалён из канала - это нельзя отменить! + Начните разговор + Нажмите Войти в канал + Нажмите, чтобы открыть + Вы можете поделиться ссылкой или QR-кодом - любой сможет вступить в канал. + Изменить настройки канала могут только владельцы канала. + Одноразовая ссылка + Вы родились без аккаунта. + Никто не отслеживал ваши разговоры. Никто не составлял карту ваших перемещений. Конфиденциальность не была функцией - это был образ жизни. + Потом мы вышли в интернет, и каждая платформа попросила частичку вас - ваше имя, ваш номер, ваших друзей. Мы смирились с тем, что за возможность общаться приходится отдавать информацию о том, с кем мы общаемся. Каждое поколение людей и технологий жило так - телефон, электронная почта, мессенджеры, социальные сети. Казалось, что другого пути нет. + Другой путь есть. Сеть без номеров телефонов. Без имён пользователей. Без аккаунтов. Без каких-либо идентификаторов пользователей. Сеть, которая соединяет людей и передаёт зашифрованные сообщения, не зная, кто с кем связан. + Не более надёжный замок на чужой двери. Не более вежливый хозяин, который уважает вашу частную жизнь, но всё равно ведёт учёт всех посетителей. Вы не гость. Вы у себя дома. Ни один король не войдёт в ваш дом - вы суверенны. + Ваши разговоры принадлежат вам, как это всегда было до интернета. Сеть - это не место, куда вы приходите. Это место, которое вы создаёте и которым владеете. И никто не может это у вас отнять, делаете ли вы его конфиденциальным или публичным. + Древнейшая человеческая свобода - говорить с другим человеком без слежки - построенная на инфраструктуре, которая не может её предать. + Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять. + Будь свободен в своей сети. + Запись голосовых сообщений не поддерживается на вашей платформе + не защищены сквозным шифрованием. Чат-релеи могут видеть эти сообщения.]]> + Дайте собеседнику Вашу ссылку + Соединитесь по ссылке или QR + Создайте Вашу ссылку + Пригласите конфиденциально + Ссылка для одного человека + Создайте Ваш публичный адрес + Ваш публичный адрес + Любой может связаться с Вами + Ваш канал + Ссылка группы + (от владельца) + (с подписью) + Ошибка при публикации канала + Вы подписчик + Новая одноразовая ссылка + Или покажите QR лично или через видеозвонок. + Используйте этот адрес в профиле социальных сетей, на сайте или в подписи email. + Или используйте этот QR - распечатайте или покажите онлайн. + Будь свободен\nв своей сети + Конфиденциальный и безопасный обмен сообщениями. + Первая сеть, в которой Вы владеете\nсвоими контактами и группами. + Начать + Зачем создан SimpleX. + Ваш профиль + На Вашем телефоне, не на серверах. + Без аккаунта. Без номера. Без email. Без ID.\nСамое безопасное шифрование. + Введите имя профиля... + Мигрировать + Ваша сеть + Серверы сети не могут знать,\nкто с кем общается + Настроить серверы + Настроить уведомления + Обязательства сети + Открыть внешнюю ссылку? + удалено (%1$d попыток) + Ошибка сообщения + Приложение удалило это сообщение после %1$d попыток его получить. + Если Вы присоединились к каналам или создали их, они перестанут работать навсегда. + Вы перестанете получать сообщения из этого канала. История чата сохранится. + обновлён профиль канала + ошибка + ОШИБКА СОЕДИНЕНИЯ + Чат с админами + Разрешить членам группы общаться с админами. + Запретить чаты с админами. + Члены группы могут общаться с админами. + Чаты с админами запрещены. + Чаты с админами в публичных каналах не имеют E2E шифрования - используйте только с доверенными чат-релеями. + Включить чаты с админами? + Включить + Сообщения о нарушениях + Разрешить отправку личных сообщений подписчикам. + Запретить отправку личных сообщений подписчикам. + Отправлять до 100 последних сообщений новым подписчикам. + Не отправлять историю новым подписчикам. + Подписчики могут отправлять исчезающие сообщения. + Подписчики могут отправлять личные сообщения. + Прямые сообщения между подписчиками запрещены. + Подписчики могут необратимо удалять отправленные сообщения. (24 часа) + Подписчики могут добавлять реакции на сообщения. + Подписчики могут отправлять голосовые сообщения. + Подписчики могут отправлять файлы и медиа. + Подписчики могут отправлять ссылки SimpleX. + Подписчики могут отправлять сообщения о нарушениях модераторам. + До 100 последних сообщений отправляется новым подписчикам. + История не отправляется новым подписчикам. + Разрешить подписчикам общаться с админами. + Подписчики могут общаться с админами. + Чаты с членами группы отключены + Публичные каналы - говорите свободно 🚀 + Надёжность: несколько релеев на каждый канал. + Владение: Вы можете запустить свои собственные релеи. + Безопасность: владельцы хранят ключи канала. + Конфиденциальность: для владельцев и подписчиков. + Проще пригласить друзей 👋 + Мы упростили подключение для новых пользователей. + Безопасные веб-ссылки + - включение картинок ссылок.\n- использовать SOCKS-прокси, если включен\n- защита от фишинга.\n- удаление трекинга ссылок. + Некоммерческое управление + Чтобы сохранить сеть SimpleX для всех. + Вы + Имя релея по умолчанию + Адрес релея по умолчанию + Ваше имя релея + Ваш адрес релея + Использовать релей + Тест релея + Использовать для новых каналов + Протестируйте релей, чтобы получить его имя.]]> + Тест релея не пройден! + Получить ссылку + Расшифровать ссылку + Ожидание ответа + Проверить + Ошибка теста на шаге %s. + ошибка + новый + Все релеи недоступны + Добавление релеев будет поддерживаться позже. + Ожидает, когда владелец канала добавит релеи. + через %1$s + Подписчики используют ссылку релея для подключения к каналу.\nАдрес релея был использован для настройки этого релея для канала. + Вы подключились к каналу через эту ссылку релея. + Соединение достигло лимита недоставленных сообщений + Ошибка сети + Ваш профиль %1$s будет отправлен чат-релеям и подписчикам канала.\nРелеи могут видеть сообщения канала. + ошибка + Не все релеи подключены + Подождать + Ваш канал + Разблокировать подписчика для всех? + Нижнее меню + Картинка ссылки будет загружена через SOCKS-прокси. DNS-запрос может быть локальным через Ваш резолвер. + Верхнее меню diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 411cbde4c4..c355d8d9fb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -376,7 +376,6 @@ วิธีใช้มาร์กดาวน์ สิ้นสุดลงแล้ว มันทำงานอย่างไร - วิธีการ SimpleX ทํางานอย่างไร กระจายอำนาจแล้ว การโทรเสียงแบบ encrypted จากต้นจนจบ การโทรวิดีแบบ encrypted จากต้นจนจบ @@ -909,7 +908,7 @@ แสดง: แสดงตัวเลือกสําหรับนักพัฒนาซอฟต์แวร์ แชร์ลิงก์ - แชร์ที่อยู่กับผู้ติดต่อ\? + แชร์ที่อยู่กับผู้ติดต่อ? แชร์กับผู้ติดต่อ บันทึกการตั้งค่า\? แสดง @@ -1328,4 +1327,4 @@ ในการตอบกลับถึง ไม่มีประวัติ encryptionใช้ได้ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 16d821637b..0e9c54fb87 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -611,7 +611,6 @@ Gizle Konuşulan kişileri ve mesajları gizle Uygulamayı, son kullanılanlar kısmından gizle. - SimpleX nasıl çalışıyor bir görüntülü aramada karşıdakine karekodunu gösterebilir ya da konuştuğun kişiye bir katılım bağlantısı paylaşabilirsin.]]> bir görüntülü aramada karşıdakinin karekodunu okutabilirsin ya da konuştuğun kişi seninle bir katılım bağlantısı paylaşabilir.]]> Eğer yüz yüze görüşemiyorsanız bir görüntülü aramada karşıdakine karekodunu gösterebilir ya da konuştuğun kişiye bir katılım bağlantısı paylaşabilirsin. @@ -2055,7 +2054,6 @@ Sunucu istatistikleri sıfırlanacaktır - bu geri alınamaz! Erişilebilir uygulama araç çubukları Görünüm ayarlarından değiştirebilirsiniz. - Sohbet listesini değiştir: Sistem modu Erişilebilir sohbet araç çubuğu İçin bilgi gösteriliyor @@ -2270,7 +2268,6 @@ Arkaplan servisi yok Kabul Et SimpleX Chat\'i kullanarak şunları kabul etmiş olursunuz:\n- genel gruplarda sadece yasal içerik göndermeyi.\n- diğer kullanıcılara saygı göstermeyi - spam yapmamayı. - Sunucu operatörlerini yapılandırma Sohbetten çıkılsın mı? yönetici Bütün sunucular diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 6d498ef4ed..4e62631dbb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -407,7 +407,6 @@ очікування підтвердження… Приватність перевизначена Ви вирішуєте, хто може під\'єднатися. - Як працює SimpleX зашифрований e2e аудіовиклик Відкрийте SimpleX Chat для прийняття виклику e2e зашифровано @@ -1887,7 +1886,6 @@ Нове повідомлення Створити Запросити - Перемикнути список чатів: Ви можете змінити це в налаштуваннях зовнішнього вигляду. Статус повідомлення Архівувати контакти, щоб поговорити пізніше. @@ -2376,7 +2374,6 @@ Приватні чати, групи та ваші контакти недоступні для операторів сервера. Прийняти Використовуючи SimpleX Chat, ви погоджуєтесь на:\n- надсилати тільки легальний контент у публічних групах.\n- поважати інших користувачів – без спаму. - Налаштувати операторів сервера Політика конфіденційності та умови використання Це посилання вимагає новішої версії додатку. Будь ласка, оновіть додаток або попросіть вашого контакту надіслати сумісне посилання. Повне посилання diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index ae22c40277..235158585d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -880,7 +880,6 @@ Hồ sơ trò chuyện ẩn Cách sử dụng Cách thức hoạt động - Cách thức SimpleX hoạt động Cách làm Lỗi khởi động WebView. Hãy đảm bảo bạn đã cài đặt WebView và kiến trúc hỗ trợ của nó là arm64.\nLỗi: %s giờ @@ -1983,7 +1982,6 @@ Tin nhắn này đã bị xóa hoặc vẫn chưa được nhận. Mã QR này không phải là một đường dẫn! Đường dẫn này không phải là một đường dẫn kết nối hợp lệ! - Chuyển đổi danh sách trò chuyện: Thời gian chờ đã hết trong khi kết nối tới máy tính Để cho phép một ứng dụng di động kết nối tới máy tính, mở cổng này trong tường lửa của bạn, nếu bạn có bật nó lên Để bảo vệ sự riêng tư của bạn, SimpleX sử dụng các ID riêng biệt cho mỗi liên hệ bạn có. @@ -2351,7 +2349,6 @@ Bằng việc sử dụng SimpleX Chat, bạn đồng ý:\n- chỉ gửi nội dung hợp pháp trong các nhóm công khai.\n- tôn trọng những người dùng khác - không gửi tin rác. Các cuộc trò chuyện riêng tư, nhóm và liên hệ của bạn không thể truy cập được đối với các bên vận hành máy chủ. Chấp nhận - Định cấu hình các bên vận hành máy chủ Đường dẫn này yêu cầu một phiên bản ứng dụng mới hơn. Vui lòng nâng cấp ứng dụng hoặc yêu cầu liên hệ của một gửi cho một đường dẫn tương thích. Đường dẫn kênh SimpleX Đường dẫn kết nối không được hỗ trợ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index be037e8b87..1392d7b42b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -618,7 +618,6 @@ %d 小时 %d 月 %d 秒 - SimpleX 是如何工作的 确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。 确保 SMP 服务器地址格式正确、每行分开且不重复。 Markdown 帮助 @@ -1207,7 +1206,7 @@ 消息回应 该聊天禁用了消息回应。 如果你在打开应用程序时输入自毁密码: - 个人资料更新将被发送给你的联系人。 + 个人资料更新将发送给你的联系人。 记录更新于 禁止消息回应。 已收到于 @@ -1350,7 +1349,7 @@ 为所有人启用 需要重新协商加密 已关闭送达回执! - 请注意:消息和文件中继通过 SOCKS 代理连接。呼叫和发送链接预览使用直接连接。]]> + 请注意:消息和文件中继通过 SOCKS 代理连接。通话使用直接连接。]]> 加密本地文件 为存储的文件和媒体加密 全新桌面应用! @@ -1848,7 +1847,7 @@ 尚无直接连接,消息由管理员转发。 其他 SMP 服务器 其他 XFTP 服务器 - 扫描/粘贴链接 + 粘贴链接/扫描 显示百分比 不活跃 缩放 @@ -2012,7 +2011,6 @@ 连接和服务器状态。 最多同时删除 20 条消息 它保护你的 IP 地址和连接。 - 切换聊天列表: 你可以在“外观”设置中更改它。 保存并重新连接 TCP 连接 @@ -2362,9 +2360,8 @@ 将从聊天中移除这些成员 — 此操作无法撤销! 隐私政策和使用条款。 接受 - 使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。 - 服务器运营方无法访问私密聊天、群和你的联系人。 - 配置服务器运营方 + 您承诺:\n- 在公开群中只发送合法内容\n- 尊重其他用户—无垃圾信息 + 运营者承诺:\n- 独立\n- 最小化元数据使用\n- 运行经验证的开源代码 不支持的连接链接 SimpleX 频道链接 短链接 @@ -2556,7 +2553,6 @@ 活跃 为所有人拦截订阅者? 广播 - 取消 取消创建频道? 测试中继 来获取其名称。]]> %1$s 频道的链接!]]> @@ -2581,9 +2577,9 @@ 连接 已连接 正在连接 - 创建频道 - 创建频道 - 创建频道(测试版) + 创建公开频道 + 创建公开频道 + 创建公开频道(测试版) 正在创建频道 解码链接 删除频道 @@ -2617,7 +2613,6 @@ 所有者 预设中继地址 预设中继名 - 继续 中继 中继 中继地址 @@ -2651,8 +2646,143 @@ 你通过此中继链接连接至该频道。 你的频道 你的频道 - 你的个人资料 %1$s 将分享给频道中继和订阅者。 + 你的个人资料 %1$s 将分享给频道中继和订阅者。中继可以访问频道消息。 你的中继地址 你的中继名 你会停止收到来自该频道的消息。聊天记录将被保留。 + 完整的频道名: + 频道资料存储在订阅者设备和聊天中继上。 + 频道资料已更新 + %d 个频道事件 + 删除了频道 + 被丢弃 (%1$d 次尝试) + 错误: %s + 保存频道资料出错 + 消息错误 + 保存并通知频道订阅者 + 保存频道资料 + 应用在尝试接收这条消息 %1$d 次后删除了它。 + 更新了频道资料 + %2$d 个中继中的 %1$d 个活跃, %3$d 个错误 + %2$d 个中继中的 %1$d 个活跃, %3$d 个被删除 + %2$d 个中继中的 %1$d 个已连接, %3$d 个失灵 + %2$d 个中继中的 %1$d 个已连接, %3$d 个被删除 + %1$d 个中继失灵 + %1$d 个中继不活跃 + 删除了 %1$d 个中继 + 目前不支持添加中继。 + 所有中继均失灵 + 删除了所有中继 + 无法广播 + 频道无活跃中继。请稍后尝试加入。 + 频道暂时不可用 + 不活跃 + 无活跃中继 + 被运营方删除 + 正等到频道所有者添加中继。 + 营业地址 + 频道链接 + 联系地址 + 分享频道出错 + (来自所有者) + 群链接 + 链接签名已验证。 + 一次性链接 + 分享频道… + 经聊天分享 + ⚠️ 签名验证失败:%s。 + (已签名) + 轻触打开 + 发送链接预览可能会将你的 IP 地址暴露给网站。你可以稍后在“隐私”设置中更改此设置。 + 连接达到了未送达消息的上限 + 禁用 + 启用 + 启用链接预览吗? + 错误 + 网络错误 + 中继结果: + 频道首选项 + 仅频道所有者可改变频道首选项。 + 用于单人进行连接的链接 + 频道 + 通过链接或二维码连接 + 创建链接 + 创建你的公开地址 + 邀请好友更简单 👋 + 给任何要和你联系的人 + 私下邀请某人 + 让某人和你连接 + 新建一次性链接 + 非盈利治理 + - 可选发送链接预览\n- 如启用则使用 SOCKS 代理\n- 防止超链接钓鱼\n- 删除链接跟踪。 + 面对面或通过视频通话展示二维码。 + 或使用此二维码 — 打印或在线展示。 + 所有权:你可以运行自己的中继。 + 隐私:对所有者和订阅者。 + 公开频道 — 畅所欲言 🚀 + 可靠性:一个频道众多中继。 + 安全的 web 链接 + 安全性:所有者持有频道密钥。 + 通过任何通讯应用发送链接 — 这是安全的。请求粘贴到 SimpleX 中。 + 和某人交谈 + 让 SimpleX 网络持续。 + 在社交媒体资料、网站或电子邮件签名中使用该地址。 + 我们让连接对新用户更简单。 + 你的公开地址 + 你生来就没有账户。 + 没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。 + 然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。 + 还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。 + 别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。 + 你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。 + 人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。 + 因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。 + 在你的网络中自由畅行。 + 允许成员与管理员聊天。 + 允许发送私信给订阅者。 + 允许订阅者与管理员聊天。 + 在您的网络中\n自由驰骋 + 禁止与管理员聊天。 + 与管理员在公开频道中聊天没有端到端加密 — 请只在受信任聊天中继中使用。 + 禁止与成员聊天 + 与管理员聊天 + 禁止订阅者间发送私信。 + 不向新订阅者发送历史记录。 + 允许 + 允许和管理员聊天? + 输入个人资料名… + 开始 + 未发送历史记录给新订阅者。 + 成员可以和管理员聊天。 + 非端到端加密。聊天中继可以看到这些消息。]]> + 迁移 + 网络承诺 + 网络路由器无法\n知道谁和谁交谈 + 无账户。无手机号。无电子邮箱。无 ID。\n最安全的加密。 + 在您的手机上,不在服务器上。 + 打开外部链接? + 私密和安全的消息收发。 + 禁止和管理员聊天。 + 禁止发送私信给订阅者。 + 发送最多 100 条最近消息给新订阅者。 + 通知设置 + 路由器设置 + 订阅者举报 + 订阅者可以添加消息回应。 + 订阅者可以和管理员聊天。 + 订阅者可以不可逆地删除已发送的消息。(24 小时) + 订阅者可以向协管举报消息。 + 订阅者可以发送私信。 + 订阅者可以发送限时消息。 + 订阅者可以发送文件和媒体。 + 订阅者可发送 SimpleX 链接。 + 订阅者可发送语音消息。 + 首个您拥有\n您的联系人和群的网络。 + 已向新订阅者发送了最多 100 条最近的消息。 + 为何打造 SimpleX。 + 您的网络 + 您的个人资料 + 底部栏 + 将通过 SOCKS5 代理请求链接预览。DNS 查询仍可能通过你的 DNS 解析器在本地发生。 + 顶部栏 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 05997a9fec..9ec116058a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -847,7 +847,6 @@ 感謝用戶 - 使用 Weblate 的翻譯貢獻! 正在修改聯絡地址為 %s … 受加密的資料庫密碼會再次更新和儲存於金鑰庫。 - SimpleX 是怎樣運作 當發生: \n1. 訊息將在傳送至客戶端後兩天或在伺服器內三十天時過時。 \n2. 訊息解密失敗,因為你或你的聯絡人用了舊的資料庫備份 \n3. 連接被破壞。 只有客戶端裝置儲存個人檔案、聯絡人、群組,和訊息。 請放置你的密碼於安全的地方,如果你遺失了密碼,將不可能修改你的密碼。 @@ -2061,7 +2060,6 @@ 停用自動刪除訊息? 刪除或審查最多 200 條訊息。 於網路和伺服器設定中啟用 Flux 以獲得更好的元資料隱私。 - 配置伺服器營運者 使用條款 更好的隱私和安全性 你可以再試一次。 diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel.svg new file mode 100644 index 0000000000..2325330d90 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel_light.svg new file mode 100644 index 0000000000..2325330d90 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_profile.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_profile.svg new file mode 100644 index 0000000000..2325330d90 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_profile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_profile_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_profile_light.svg new file mode 100644 index 0000000000..2325330d90 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_profile_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt index 59d71a83f1..8d26f2f085 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt @@ -14,6 +14,8 @@ import java.util.* import kotlin.math.max internal val vlcFactory: MediaPlayerFactory by lazy { MediaPlayerFactory() } +// No hardware acceleration - more secure for previews +internal val vlcPreviewFactory: MediaPlayerFactory by lazy { MediaPlayerFactory("--avcodec-hw=none") } actual class RecorderNative: RecorderInterface { private var player: MediaPlayer? = null diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt index f88c539284..90c80d3b2a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt @@ -225,9 +225,9 @@ actual class VideoPlayer actual constructor( player.media().startPaused(uri.toFile().absolutePath) val start = System.currentTimeMillis() var snap: BufferedImage? = null - while (snap == null && start + 5000 > System.currentTimeMillis()) { + while (snap == null && start + 1500 > System.currentTimeMillis()) { snap = player.snapshots()?.get() - delay(10) + delay(50) } val orientation = player.media().info().videoTracks().firstOrNull()?.orientation() if (orientation == null) { @@ -280,7 +280,7 @@ actual class VideoPlayer actual constructor( private fun putPlayer(player: Component) = playersPool.add(player) - private fun getOrCreateHelperPlayer(): CallbackMediaPlayerComponent = helperPlayersPool.removeFirstOrNull() ?: CallbackMediaPlayerComponent(MediaPlayerSpecs.callbackMediaPlayerSpec().apply { withFactory(vlcFactory) }) + private fun getOrCreateHelperPlayer(): CallbackMediaPlayerComponent = helperPlayersPool.removeFirstOrNull() ?: CallbackMediaPlayerComponent(MediaPlayerSpecs.callbackMediaPlayerSpec().apply { withFactory(vlcPreviewFactory) }) private fun putHelperPlayer(player: CallbackMediaPlayerComponent) = helperPlayersPool.add(player) } } diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 45129a0d54..09cf90553f 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,13 +24,13 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.5-beta.9 -android.version_code=341 +android.version_name=6.5.1 +android.version_code=347 android.bundle=false -desktop.version_name=6.5-beta.9 -desktop.version_code=136 +desktop.version_name=6.5.1 +desktop.version_code=142 kotlin.version=2.1.20 gradle.plugin.version=8.7.0 diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 45c0b84cc6..bfbc025a49 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -33,10 +33,10 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Directory.Store import Simplex.Chat.Controller -import Simplex.Chat.Markdown (displayNameTextP) +import Simplex.Chat.Markdown (MarkdownList, displayNameTextP) import Simplex.Chat.Messages import Simplex.Chat.Messages.CIContent -import Simplex.Chat.Protocol (MsgContent (..)) +import Simplex.Chat.Protocol (LinkOwnerSig, MsgChatLink, MsgContent (..)) import Simplex.Chat.Types import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol (AgentErrorType (..)) @@ -49,6 +49,7 @@ data DirectoryEvent | DEGroupInvitation {contact :: Contact, groupInfo :: GroupInfo, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole} | DEServiceJoinedGroup {contactId :: ContactId, groupInfo :: GroupInfo, hostMember :: GroupMember} | DEGroupUpdated {member :: GroupMember, fromGroup :: GroupInfo, toGroup :: GroupInfo} + | DEGroupLinkCheck GroupInfo | DEPendingMember GroupInfo GroupMember | DEPendingMemberMsg GroupInfo GroupMember ChatItemId Text | DEContactRoleChanged GroupInfo ContactId GroupMemberRole -- contactId here is the contact whose role changed @@ -57,6 +58,8 @@ data DirectoryEvent | DEContactLeftGroup ContactId GroupInfo | DEServiceRemovedFromGroup GroupInfo | DEGroupDeleted GroupInfo + | DEChatLinkReceived {contact :: Contact, chatItemId :: ChatItemId, chatLink :: MsgChatLink, ownerSig :: Maybe LinkOwnerSig} + | DEMemberUpdated {groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember} | DEUnsupportedMessage Contact ChatItemId | DEItemEditIgnored Contact | DEItemDeleteIgnored Contact @@ -91,11 +94,14 @@ crDirectoryEvent_ = \case CEvtLeftMember {groupInfo, member} -> (`DEContactLeftGroup` groupInfo) <$> memberContactId member CEvtDeletedMemberUser {groupInfo} -> Just $ DEServiceRemovedFromGroup groupInfo CEvtGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo + CEvtUnknownMemberAnnounced {groupInfo, unknownMember, announcedMember} -> Just $ DEMemberUpdated {groupInfo, fromMember = unknownMember, toMember = announcedMember} + CEvtGroupMemberUpdated {groupInfo, fromMember, toMember} -> Just $ DEMemberUpdated {groupInfo, fromMember, toMember} CEvtChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct CEvtChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct - CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} -> + CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, formattedText = ft, meta = CIMeta {itemLive}}) : _} -> Just $ case (mc, itemLive) of - (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t + (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP ft <* A.endOfInput) $ T.dropWhileEnd isSpace t + (MCChat {chatLink, ownerSig}, Nothing) -> DEChatLinkReceived {contact = ct, chatItemId = ciId, chatLink, ownerSig} _ -> DEUnsupportedMessage ct ciId where ciId = chatItemId' ci @@ -149,7 +155,7 @@ data DirectoryHelpSection = DHSRegistration | DHSCommands data DirectoryCmd (r :: DirectoryRole) where DCHelp :: DirectoryHelpSection -> DirectoryCmd 'DRUser - DCSearchGroup :: Text -> DirectoryCmd 'DRUser + DCSearchGroup :: Text -> Maybe MarkdownList -> DirectoryCmd 'DRUser DCSearchNext :: DirectoryCmd 'DRUser DCAllGroups :: DirectoryCmd 'DRUser DCRecentGroups :: DirectoryCmd 'DRUser @@ -181,11 +187,11 @@ data ADirectoryCmd = forall r. ADC (SDirectoryRole r) (DirectoryCmd r) deriving instance Show ADirectoryCmd -directoryCmdP :: Parser ADirectoryCmd -directoryCmdP = +directoryCmdP :: Maybe MarkdownList -> Parser ADirectoryCmd +directoryCmdP ft = (A.char '/' *> cmdStrP) <|> (A.char '.' $> ADC SDRUser DCSearchNext) - <|> (ADC SDRUser . DCSearchGroup <$> A.takeText) + <|> (ADC SDRUser . (`DCSearchGroup` ft) <$> A.takeText) where cmdStrP = (tagP >>= \(ADCT u t) -> ADC u <$> (cmdP t <|> pure (DCCommandError t))) @@ -304,7 +310,7 @@ directoryCmdP = directoryCmdTag :: DirectoryCmd r -> Text directoryCmdTag = \case DCHelp _ -> "help" - DCSearchGroup _ -> "search" + DCSearchGroup {} -> "search" DCSearchNext -> "next" DCAllGroups -> "all" DCRecentGroups -> "new" diff --git a/apps/simplex-directory-service/src/Directory/Listing.hs b/apps/simplex-directory-service/src/Directory/Listing.hs index 0d4e8d351c..ef093020bb 100644 --- a/apps/simplex-directory-service/src/Directory/Listing.hs +++ b/apps/simplex-directory-service/src/Directory/Listing.hs @@ -27,7 +27,7 @@ import Data.List (isPrefixOf) import Data.Maybe (catMaybes, fromMaybe) import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) +import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.Time.Clock import Data.Time.Clock.System import Data.Time.Format.ISO8601 (iso8601Show) @@ -53,16 +53,24 @@ listingImageFolder :: String listingImageFolder = "images" data DirectoryEntryType = DETGroup - { admission :: Maybe GroupMemberAdmission, + { groupType :: Maybe GroupType, + admission :: Maybe GroupMemberAdmission, summary :: GroupSummary } $(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "DET") ''DirectoryEntryType) +data PublicLink = PublicLink + { connFullLink :: Maybe ConnReqContact, + connShortLink :: Maybe ShortLinkContact + } + +$(JQ.deriveJSON defaultJSON ''PublicLink) + data DirectoryEntry = DirectoryEntry { entryType :: DirectoryEntryType, displayName :: Text, - groupLink :: CreatedLinkContact, + groupLink :: PublicLink, shortDescr :: Maybe MarkdownList, welcomeMessage :: Maybe MarkdownList, imageFile :: Maybe String, @@ -90,8 +98,15 @@ recentRoundedTime roundTo now t groupDirectoryEntry :: UTCTime -> GroupInfo -> Maybe GroupLink -> Maybe (DirectoryEntry, Maybe (FilePath, ImageFileData)) groupDirectoryEntry now GroupInfo {groupProfile, chatTs, createdAt, groupSummary} gLink_ = - let GroupProfile {displayName, shortDescr, description, image, memberAdmission} = groupProfile - entryType = DETGroup memberAdmission groupSummary + let GroupProfile {displayName, shortDescr, description, image, memberAdmission, publicGroup} = groupProfile + gt = (\PublicGroupProfile {groupType} -> groupType) <$> publicGroup + entryType = DETGroup gt memberAdmission groupSummary + description' = case publicGroup of + Just PublicGroupProfile {groupType = gt', groupLink = sLnk} -> + let gtStr = case gt' of GTChannel -> "channel"; _ -> "group" + linkLine = "Link to join the " <> gtStr <> " " <> displayName <> ": " <> decodeUtf8 (strEncode sLnk) + in Just $ maybe linkLine (<> "\n\n" <> linkLine) description + Nothing -> description entry groupLink = let de = DirectoryEntry @@ -99,22 +114,30 @@ groupDirectoryEntry now GroupInfo {groupProfile, chatTs, createdAt, groupSummary displayName, groupLink, shortDescr = toFormattedText <$> shortDescr, - welcomeMessage = toFormattedText <$> description, + welcomeMessage = toFormattedText <$> description', imageFile = fst <$> imgData, activeAt = recentRoundedTime 900 now $ fromMaybe createdAt chatTs, createdAt = recentRoundedTime 86400 now createdAt } imgData = imgFileData groupLink =<< image in (de, imgData) - in (entry . connLinkContact) <$> gLink_ + in case publicGroup of + Just PublicGroupProfile {groupLink = sLnk} -> + Just $ entry $ PublicLink Nothing (Just sLnk) + Nothing -> + entry . toPublicLink . connLinkContact <$> gLink_ where - imgFileData :: CreatedConnLink 'CMContact -> ImageData -> Maybe (FilePath, ByteString) - imgFileData groupLink (ImageData img) = + toPublicLink (CCLink fullLink shortLink) = PublicLink (Just fullLink) shortLink + imgFileData :: PublicLink -> ImageData -> Maybe (FilePath, ByteString) + imgFileData PublicLink {connFullLink, connShortLink} (ImageData img) = let (img', imgExt) = fromMaybe (img, ".jpg") $ (,".jpg") <$> T.stripPrefix "data:image/jpg;base64," img <|> (,".png") <$> T.stripPrefix "data:image/png;base64," img - imgName = B.unpack $ B64URL.encodeUnpadded $ BA.convert $ (CH.hash :: ByteString -> Digest MD5) $ strEncode (connFullLink groupLink) + linkHash = case connFullLink of + Just fl -> strEncode fl + Nothing -> maybe "" strEncode connShortLink + imgName = B.unpack $ B64URL.encodeUnpadded $ BA.convert $ (CH.hash :: ByteString -> Digest MD5) linkHash imgFile = listingImageFolder imgName <> imgExt in case B64.decode $ encodeUtf8 img' of Right img'' -> Just (imgFile, img'') diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index 94305abaa2..f566ed5ded 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -42,6 +42,7 @@ data DirectoryOpts = DirectoryOpts runCLI :: Bool, searchResults :: Int, webFolder :: Maybe FilePath, + linkCheckInterval :: Int, testing :: Bool } @@ -162,6 +163,14 @@ directoryOpts appDir defaultDbName = do <> metavar "WEB_FOLDER" <> help "Folder to store static web assets" ) + linkCheckInterval <- + option + auto + ( long "link-check-interval" + <> metavar "SECONDS" + <> help "Interval in seconds to check public group link data (default: 1800)" + <> value 1800 + ) pure DirectoryOpts { coreOptions, @@ -182,6 +191,7 @@ directoryOpts appDir defaultDbName = do runCLI, searchResults = 10, webFolder, + linkCheckInterval, testing = False } diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 34b63ff06a..6e414ef011 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -18,7 +18,7 @@ module Directory.Service ) where -import Control.Concurrent (forkIO) +import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.STM import Control.Exception (SomeException, try) import Control.Logger.Simple @@ -31,7 +31,7 @@ import Data.Either (fromRight) import Data.List (find, intercalate) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Map.Strict as M -import Data.Maybe (fromMaybe, isJust, isNothing) +import Data.Maybe (fromMaybe, isJust, isNothing, maybeToList) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T @@ -51,12 +51,12 @@ import Simplex.Chat.Bot import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller import Simplex.Chat.Core -import Simplex.Chat.Markdown (Format (..), FormattedText (..), parseMaybeMarkdownList, viewName) +import Simplex.Chat.Markdown (Format (..), FormattedText (..), SimplexLinkType (..), parseMaybeMarkdownList, viewName) import Simplex.Chat.Messages import Simplex.Chat.Options -import Simplex.Chat.Protocol (MsgContent (..), memberSupportVoiceVersion) +import Simplex.Chat.Protocol (GroupShortLinkData (..), LinkOwnerSig (..), MsgChatLink (..), MsgContent (..), memberSupportVoiceVersion) import Simplex.Chat.Store.Direct (getContact) -import Simplex.Chat.Store.Groups (getGroupLink, getGroupMember, setGroupCustomData) -- TODO remove setGroupCustomData +import Simplex.Chat.Store.Groups (getGroupLink, getGroupMember, getGroupMemberByMemberId, setGroupCustomData) -- TODO remove setGroupCustomData import Simplex.Chat.Store.Profiles (GroupLinkInfo (..), getGroupLinkInfo) import Simplex.Chat.Store.Shared (StoreError (..)) import Simplex.Chat.Terminal (terminalChatConfig) @@ -65,9 +65,10 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatError, serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) -import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnectionLink (..), CreatedConnLink (..), SConnectionMode (..), sameConnReqContact, sameShortLinkContact) +import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ACreatedConnLink (..), AgentErrorType (..), ConnectionLink (..), CreatedConnLink (..), SConnectionMode (..), sameConnReqContact, sameShortLinkContact) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Protocol (ErrorType (..)) import Simplex.Messaging.TMap (TMap) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Util (eitherToMaybe, raceAny_, safeDecodeUtf8, tshow, unlessM, (<$$>)) @@ -99,7 +100,9 @@ data ServiceState = ServiceState { searchRequests :: TMap ContactId SearchRequest, blockedWordsCfg :: BlockedWordsConfig, pendingCaptchas :: TMap GroupMemberId PendingCaptcha, - updateListingsJob :: TMVar ChatController + serviceCC :: TMVar ChatController, + eventQ :: TQueue DirectoryEvent, + updateListingsJob :: TMVar () } data CaptchaMode = CMText | CMAudio @@ -125,8 +128,10 @@ newServiceState opts = do searchRequests <- TM.emptyIO blockedWordsCfg <- readBlockedWordsConfig opts pendingCaptchas <- TM.emptyIO + serviceCC <- newEmptyTMVarIO + eventQ <- newTQueueIO updateListingsJob <- newEmptyTMVarIO - pure ServiceState {searchRequests, blockedWordsCfg, pendingCaptchas, updateListingsJob} + pure ServiceState {searchRequests, blockedWordsCfg, pendingCaptchas, serviceCC, eventQ, updateListingsJob} welcomeGetOpts :: IO DirectoryOpts welcomeGetOpts = do @@ -150,9 +155,8 @@ welcomeGetOpts = do directoryServiceCLI :: DirectoryLog -> DirectoryOpts -> IO () directoryServiceCLI st opts = do - env <- newServiceState opts - eventQ <- newTQueueIO - let eventHook cc resp = atomically $ resp <$ writeTQueue eventQ (cc, resp) + env@ServiceState {eventQ} <- newServiceState opts + let eventHook _cc resp = atomically $ resp <$ mapM_ (writeTQueue eventQ) (crDirectoryEvent resp) chatHooks = defaultChatHooks { preStartHook = Just $ directoryPreStartHook opts, @@ -162,31 +166,50 @@ directoryServiceCLI st opts = do } raceAny_ $ [ simplexChatCLI' terminalChatConfig {chatHooks} (mkChatOpts opts) Nothing, - processEvents eventQ env + processEvents env ] - <> updateListingsThread_ opts env + <> maybeToList (updateListingsThread_ opts env) + <> maybeToList (linkCheckThread_ opts env) where - processEvents eventQ env = forever $ do - (cc, resp) <- atomically $ readTQueue eventQ + processEvents env@ServiceState {eventQ} = do + cc <- atomically $ readTMVar $ serviceCC env u_ <- readTVarIO (currentUser cc) - forM_ u_ $ \user -> directoryServiceEvent st opts env user cc resp + forM_ u_ $ \user -> + forever $ do + event <- atomically $ readTQueue eventQ + directoryServiceEvent st opts env user cc event updateListingDelay :: Int updateListingDelay = 5 * 60 * 1000000 -- update every 5 minutes -updateListingsThread_ :: DirectoryOpts -> ServiceState -> [IO ()] -updateListingsThread_ opts env = maybe [] (\f -> [updateListingsThread f]) $ webFolder opts +updateListingsThread_ :: DirectoryOpts -> ServiceState -> Maybe (IO ()) +updateListingsThread_ opts env = updateListingsThread <$> webFolder opts where updateListingsThread f = do - cc <- atomically $ takeTMVar $ updateListingsJob env + cc <- atomically $ readTMVar $ serviceCC env forever $ do u <- readTVarIO $ currentUser cc forM_ u $ \user -> updateGroupListingFiles cc user f delay <- registerDelay updateListingDelay atomically $ void (takeTMVar $ updateListingsJob env) `orElse` unlessM (readTVar delay) retry -listingsUpdated :: ServiceState -> ChatController -> IO () -listingsUpdated env = void . atomically . tryPutTMVar (updateListingsJob env) +listingsUpdated :: ServiceState -> IO () +listingsUpdated env = void $ atomically $ tryPutTMVar (updateListingsJob env) () + +linkCheckThread_ :: DirectoryOpts -> ServiceState -> Maybe (IO ()) +linkCheckThread_ opts env@ServiceState {eventQ} + | linkCheckInterval opts > 0 = Just $ do + cc <- atomically $ readTMVar $ serviceCC env + forever $ do + threadDelay $ linkCheckInterval opts * 1000000 + u <- readTVarIO $ currentUser cc + forM_ u $ \user -> + withDB' "linkCheckThread" cc (\db -> getAllGroupRegs_ db user) >>= \case + Left e -> logError $ "linkCheckThread error: " <> T.pack e + Right grs -> forM_ grs $ \(gInfo, gr) -> + unless (groupRemoved $ groupRegStatus gr) $ + atomically $ writeTQueue eventQ $ DEGroupLinkCheck gInfo + | otherwise = Nothing directoryPreStartHook :: DirectoryOpts -> ChatController -> IO () directoryPreStartHook opts ChatController {config, chatStore} = runDirectoryMigrations opts config chatStore @@ -197,7 +220,8 @@ directoryPostStartHook opts@DirectoryOpts {noAddress, testing} env cc = Nothing -> putStrLn "No current user" >> exitFailure Just User {userId, profile = p@LocalProfile {preferences}} -> do unless noAddress $ initializeBotAddress' (not testing) cc - listingsUpdated env cc + void $ atomically $ tryPutTMVar (serviceCC env) cc + listingsUpdated env let cmds = fromMaybe [] $ preferences >>= commands_ unless (cmds == directoryCommands) $ do let prefs = (fromMaybe emptyChatPrefs preferences) {files = Just FilesPreference {allow = FANo}, commands = Just directoryCommands} :: Preferences @@ -226,7 +250,7 @@ directoryCommands = directoryService :: DirectoryLog -> DirectoryOpts -> ChatConfig -> IO () directoryService st opts cfg = do - env <- newServiceState opts + env@ServiceState {eventQ} <- newServiceState opts let chatHooks = defaultChatHooks { preStartHook = Just $ directoryPreStartHook opts, @@ -235,12 +259,15 @@ directoryService st opts cfg = do } simplexChatCore cfg {chatHooks} (mkChatOpts opts) $ \user cc -> raceAny_ $ - [ forever $ void getLine, - forever $ do + [ forever $ do (_, resp) <- atomically . readTBQueue $ outputQ cc - directoryServiceEvent st opts env user cc resp + mapM_ (atomically . writeTQueue eventQ) $ crDirectoryEvent resp, + forever $ do + event <- atomically $ readTQueue eventQ + directoryServiceEvent st opts env user cc event ] - <> updateListingsThread_ opts env + <> maybeToList (updateListingsThread_ opts env) + <> maybeToList (linkCheckThread_ opts env) acceptMemberHook :: DirectoryOpts -> ServiceState -> GroupInfo -> GroupLinkInfo -> Profile -> IO (Either GroupRejectionReason (GroupAcceptance, GroupMemberRole)) acceptMemberHook @@ -283,13 +310,13 @@ readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, na unless testing $ putStrLn $ "Blocked fragments: " <> show (length blockedFragments) <> ", blocked words: " <> show (length blockedWords) <> ", spelling rules: " <> show (M.size spelling) pure BlockedWordsConfig {blockedFragments, blockedWords, extensionRules, spelling} -directoryServiceEvent :: DirectoryLog -> DirectoryOpts -> ServiceState -> User -> ChatController -> Either ChatError ChatEvent -> IO () -directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName, ownersGroup, searchResults} env@ServiceState {searchRequests} user@User {userId} cc event = - forM_ (crDirectoryEvent event) $ \case +directoryServiceEvent :: DirectoryLog -> DirectoryOpts -> ServiceState -> User -> ChatController -> DirectoryEvent -> IO () +directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName, ownersGroup, searchResults} env@ServiceState {searchRequests} user@User {userId} cc = \case DEContactConnected ct -> deContactConnected ct DEGroupInvitation {contact = ct, groupInfo = g, fromMemberRole, memberRole} -> deGroupInvitation ct g fromMemberRole memberRole DEServiceJoinedGroup ctId g owner -> deServiceJoinedGroup ctId g owner DEGroupUpdated {member, fromGroup, toGroup} -> deGroupUpdated member fromGroup toGroup + DEGroupLinkCheck g -> deGroupLinkCheck g DEPendingMember g m -> dePendingMember g m DEPendingMemberMsg g m ciId t -> dePendingMemberMsg g m ciId t DEContactRoleChanged g ctId role -> deContactRoleChanged g ctId role @@ -298,6 +325,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName DEContactLeftGroup ctId g -> deContactLeftGroup ctId g DEServiceRemovedFromGroup g -> deServiceRemovedFromGroup g DEGroupDeleted g -> deGroupDeleted g + DEChatLinkReceived {contact = ct, chatLink, ownerSig} -> deChatLinkReceived ct chatLink ownerSig + DEMemberUpdated {groupInfo = g, fromMember, toMember} -> deMemberUpdated g fromMember toMember DEUnsupportedMessage _ct _ciId -> pure () DEItemEditIgnored _ct -> pure () DEItemDeleteIgnored _ct -> pure () @@ -325,7 +354,19 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let msg = "Error: " <> err <> ", group: " <> tshow groupId <> " " <> localDisplayName <> ", " <> T.pack e notifyAdminUsers msg logError msg - groupInfoText p@GroupProfile {description = d} = groupNameDescr p <> maybe "" ("\nWelcome message:\n" <>) d + groupInfoText p@GroupProfile {description = d, publicGroup} = groupNameDescr p <> maybe "" ("\nWelcome message:\n" <>) d <> linkToJoin + where + linkToJoin = case publicGroup of + Just pg@PublicGroupProfile {groupLink} -> + "\nLink to join " <> groupTypeStr' pg <> ": " <> strEncodeTxt groupLink + <> "\nYou need SimpleX Chat app v6.5 to join." + Nothing -> "" + membersCountStr GroupProfile {publicGroup} GroupSummary {currentMembers, publicMemberCount} = + let count = fromMaybe currentMembers publicMemberCount + label = case publicGroup of + Just PublicGroupProfile {groupType = GTChannel} -> " subscribers" + _ -> " members" + in tshow count <> label knockingStr :: Maybe GroupMemberAdmission -> [Text] knockingStr = \case Just GroupMemberAdmission {review = Just MCAll} -> ["New members are reviewed by admins"] @@ -342,6 +383,9 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName groupReference' groupId displayName = "ID " <> tshow groupId <> " (" <> displayName <> ")" groupAlreadyListed GroupInfo {groupProfile = p} = "The group " <> groupNameDescr p <> " is already listed in the directory, please choose another name." + ifPublicGroup :: GroupInfo -> IO () -> IO () -> IO () + ifPublicGroup GroupInfo {groupProfile = GroupProfile {publicGroup}} reject action = + if isJust publicGroup then reject else action getDuplicateGroup :: GroupInfo -> IO (Either String DuplicateGroup) getDuplicateGroup GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = @@ -375,7 +419,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName sendMessage cc ct $ ("Welcome to " <> serviceName <> "!\n\n") <> "🔍 Send search string to find groups - try _security_.\n\ - \/help - how to submit your group.\n\ + \/help - how to submit your group or channel.\n\ \/new - recent groups.\n\n\ \[Directory rules](https://simplex.chat/docs/directory.html)." @@ -461,37 +505,68 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName byMember = case memberContactId m of Just ctId | ctId `isOwner` gr -> "" -- group registration owner, not any group owner. _ -> " by " <> mName -- owner notification from directory will include the name. - case groupRegStatus of - GRSPendingConfirmation -> pure () - GRSProposed -> pure () - GRSPendingUpdate -> - groupProfileUpdate >>= \case - GPNoServiceLink -> - notifyOwner gr $ "The profile updated for " <> userGroupRef <> byMember <> ", but the group link is not added to the welcome message." - GPServiceLinkAdded _ -> groupLinkAdded gr byMember - GPServiceLinkRemoved -> - notifyOwner gr $ - "The group link of " <> userGroupRef <> " is removed from the welcome message" <> byMember <> ", please add it." - GPHasServiceLink {} -> groupLinkAdded gr byMember - GPServiceLinkError -> do - notifyOwner gr $ - ("Error: " <> serviceName <> " has no group link for " <> userGroupRef) - <> " after profile was updated" - <> byMember - <> ". Please report the error to the developers." - logError $ "Error: no group link for " <> userGroupRef - GRSPendingApproval n -> processProfileChange gr byMember False $ n + 1 - GRSActive -> processProfileChange gr byMember True 1 - GRSSuspended -> processProfileChange gr byMember False 1 - GRSSuspendedBadRoles -> processProfileChange gr byMember False 1 - GRSRemoved -> pure () + case publicGroup p' of + Just pg -> case groupRegStatus of + GRSPendingApproval n -> publicGroupProfileChange pg gr byMember $ n + 1 + GRSActive -> publicGroupProfileChange pg gr byMember 1 + _ -> pure () + Nothing -> case groupRegStatus of + GRSPendingConfirmation -> pure () + GRSProposed -> pure () + GRSPendingUpdate -> + groupProfileUpdate >>= \case + GPNoServiceLink -> + notifyOwner gr $ "The profile updated for " <> userGroupRef <> byMember <> ", but the group link is not added to the welcome message." + GPServiceLinkAdded _ -> groupLinkAdded gr byMember + GPServiceLinkRemoved -> + notifyOwner gr $ + "The group link of " <> userGroupRef <> " is removed from the welcome message" <> byMember <> ", please add it." + GPHasServiceLink {} -> groupLinkAdded gr byMember + GPServiceLinkError -> do + notifyOwner gr $ + ("Error: " <> serviceName <> " has no group link for " <> userGroupRef) + <> " after profile was updated" + <> byMember + <> ". Please report the error to the developers." + logError $ "Error: no group link for " <> userGroupRef + GRSPendingApproval n -> processProfileChange gr byMember False $ n + 1 + GRSActive -> processProfileChange gr byMember True 1 + GRSSuspended -> processProfileChange gr byMember False 1 + GRSSuspendedBadRoles -> processProfileChange gr byMember False 1 + GRSRemoved -> pure () where GroupInfo {groupId, groupProfile = p} = fromGroup GroupInfo {groupProfile = p'} = toGroup sameProfile - GroupProfile {displayName = n, fullName = fn, shortDescr = sd, image = i, description = d, memberAdmission = ma} - GroupProfile {displayName = n', fullName = fn', shortDescr = sd', image = i', description = d', memberAdmission = ma'} = - n == n' && fn == fn' && i == i' && sd == sd' && (T.words <$> d) == (T.words <$> d') && ma == ma' + GroupProfile {displayName = n, fullName = fn, shortDescr = sd, image = i, description = d, memberAdmission = ma, publicGroup = pg} + GroupProfile {displayName = n', fullName = fn', shortDescr = sd', image = i', description = d', memberAdmission = ma', publicGroup = pg'} = + n == n' && fn == fn' && i == i' && sd == sd' && (T.words <$> d) == (T.words <$> d') && ma == ma' && pg == pg' + publicGroupProfileChange pg@PublicGroupProfile {groupLink} gr byMember n' = do + let gt = groupTypeStr' pg + userGroupRef = userGroupReference gr toGroup + groupRef = groupReference toGroup + link = ACL SCMContact $ CLShort groupLink + updatedNotification gr' g' = do + notifyOwner gr' $ + ("The " <> gt <> " " <> userGroupRef <> " is updated" <> byMember) + <> ".\nIt is hidden from the directory until approved." + notifyAdminUsers $ "The " <> gt <> " " <> groupRef <> " is updated" <> byMember <> "." + sendToApprove g' gr' n' + sendChatCmd cc (APIConnectPlan userId (Just link) True Nothing) >>= \case + Right (CRConnectionPlan _ _ (CPGroupLink (GLPKnown {groupInfo = g'}))) -> + case dbOwnerMemberId gr of + Just ownerGMId -> + withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMember db (vr cc) user groupId ownerGMId) >>= \case + Right ownerMember + | let GroupMember {memberRole = role} = ownerMember, role >= GROwner -> + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n') (`updatedNotification` g') + | otherwise -> do + setGroupStatus notifyAdminUsers st env cc groupId GRSSuspendedBadRoles $ \_ -> pure () + notifyOwner gr $ "The registration owner is no longer an owner. Registration suspended." + Left _ -> logError $ "could not find owner member for " <> groupRef + Nothing -> logError $ "no owner member set for " <> groupRef + _ -> + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n') (`updatedNotification` toGroup) groupLinkAdded gr byMember = getDuplicateGroup toGroup >>= \case Left e -> notifyOwner gr $ "Error: getDuplicateGroup. Please notify the developers.\n" <> T.pack e @@ -644,7 +719,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName -- /audio is matched as text, not as DirectoryCmd, because it is only valid -- in group context at captcha stage, while DirectoryCmd is for DM commands. isAudioCmd = T.strip msgText == "/audio" - cmd = fromRight (ADC SDRUser DCUnknownCommand) $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.strip msgText + cmd = fromRight (ADC SDRUser DCUnknownCommand) $ A.parseOnly (directoryCmdP Nothing <* A.endOfInput) $ T.strip msgText atomically (TM.lookup gmId $ pendingCaptchas env) >>= \case Nothing | isAudioCmd && canSendVoiceCaptcha g m -> sendMemberCaptcha g m (Just ciId) noCaptcha 0 CMAudio @@ -661,7 +736,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName sendComposedMessages_ cc sendRef [(Just ciId, MCText audioAlreadyEnabled)] else sendComposedMessages_ cc sendRef [(Just ciId, MCText voiceCaptchaUnavailable)] | otherwise -> case cmd of - ADC SDRUser (DCSearchGroup _) -> do + ADC SDRUser (DCSearchGroup {}) -> do ts <- getCurrentTime if | ts `diffUTCTime` sentAt > captchaTTL -> sendMemberCaptcha g m (Just ciId) captchaExpired (attempts - 1) captchaMode @@ -704,17 +779,59 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName useMemberFilter image $ passCaptcha a sendToApprove :: GroupInfo -> GroupReg -> GroupApprovalId -> IO () - sendToApprove GroupInfo {groupId, groupProfile = p@GroupProfile {displayName, image = image'}, groupSummary} GroupReg {dbContactId, promoted} gaId = do + sendToApprove GroupInfo {groupId, groupProfile = p@GroupProfile {displayName, image = image', publicGroup = pg_}, groupSummary} GroupReg {dbContactId, promoted} gaId = do ct_ <- getContact' cc user dbContactId - let membersStr = "_" <> tshow (currentMembers groupSummary) <> " members_\n" + let gt = maybe "group" groupTypeStr' pg_ + membersStr = "_" <> membersCountStr p groupSummary <> "_\n" text = - either (\_ -> "The group ID " <> tshow groupId <> " submitted: ") (\c -> localDisplayName' c <> " submitted the group ID " <> tshow groupId <> ": ") ct_ + either (\_ -> "The " <> gt <> " ID " <> tshow groupId <> " submitted: ") (\c -> localDisplayName' c <> " submitted the " <> gt <> " ID " <> tshow groupId <> ": ") ct_ <> ("\n" <> groupInfoText p <> "\n" <> membersStr <> "\nTo approve send:") msg = maybe (MCText text) (\image -> MCImage {text, image}) image' withAdminUsers $ \cId -> do let approveCmd = MCText $ "/approve " <> tshow groupId <> ":" <> viewName displayName <> " " <> tshow gaId <> if promoted then " promote=on" else "" sendComposedMessages cc (SRDirect cId) [msg, approveCmd] + deGroupLinkCheck :: GroupInfo -> IO () + deGroupLinkCheck gInfo@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}, groupSummary = summary} = + withGroupReg gInfo "link check" $ \gr@GroupReg {groupRegStatus, dbOwnerMemberId} -> + forM_ pg_ $ \pg@PublicGroupProfile {groupLink} -> + when (groupRegStatus == GRSActive || pendingApproval groupRegStatus) $ do + let link = ACL SCMContact $ CLShort groupLink + sendChatCmd cc (APIConnectPlan userId (Just link) True Nothing) >>= \case + Right (CRConnectionPlan _ _ (CPGroupLink (GLPKnown {groupInfo = g', groupUpdated = BoolDef updated, linkOwners = ListDef owners}))) -> + checkValidOwner dbOwnerMemberId owners $ do + when updated $ reapprove pg gr groupRegStatus g' + when (updated || summary /= groupSummary g') $ listingsUpdated env + Left (ChatErrorAgent {agentError = SMP _ err}) | linkDeleted err -> + setGroupStatus logError st env cc groupId GRSRemoved $ \gr' -> + notifyOwner gr' "The channel link is no longer valid.\nThe channel is removed from the directory." + _ -> pure () + where + linkDeleted = \case + AUTH -> True + BLOCKED {} -> True + _ -> False + checkValidOwner dbOwnerMemberId owners onValid = case dbOwnerMemberId of + Just ownerGMId -> + withDB "checkGroupLink" cc (\db -> withExceptT show $ getGroupMember db (vr cc) user groupId ownerGMId) >>= \case + Right GroupMember {memberId, memberPubKey} + | any (\GroupLinkOwner {memberId = mId, memberKey} -> memberId == mId && memberPubKey == Just memberKey) owners -> onValid + _ -> setGroupStatus logError st env cc groupId GRSSuspendedBadRoles $ \gr' -> + notifyOwner gr' "The registration owner is no longer a channel owner.\nThe channel is no longer listed in the directory." + Nothing -> onValid + reapprove pg gr groupRegStatus g' = do + let gt = groupTypeStr' pg + groupRef = groupReference gInfo + notifyAdminUsers $ "The " <> gt <> " " <> groupRef <> " profile changed." + case groupRegStatus of + GRSActive -> + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval 1) $ \gr' -> do + notifyOwner gr' $ "The " <> gt <> " profile has changed.\nIt is hidden from the directory until approved." + sendToApprove g' gr' 1 + GRSPendingApproval n -> + sendToApprove g' gr (n + 1) + _ -> pure () + deContactRoleChanged :: GroupInfo -> ContactId -> GroupMemberRole -> IO () deContactRoleChanged g@GroupInfo {groupId, membership = GroupMember {memberRole = serviceRole}} ctId contactRole = do logInfo $ "contact ID " <> tshow ctId <> " role changed in group " <> viewGroupName g <> " to " <> tshow contactRole @@ -771,63 +888,205 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName >>= mapM_ (\cm@GroupMember {memberRole} -> when (memberRole == GROwner && memberActive cm) action) deContactRemovedFromGroup :: ContactId -> GroupInfo -> IO () - deContactRemovedFromGroup ctId g@GroupInfo {groupId} = do + deContactRemovedFromGroup ctId g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "contact ID " <> tshow ctId <> " removed from group " <> viewGroupName g - withGroupReg g "contact removed" $ \gr -> do + withGroupReg g "contact removed" $ \gr -> when (ctId `isOwner` gr) $ setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr' -> do - notifyOwner gr' $ "You are removed from the group " <> userGroupReference gr' g <> ".\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner is removed)." + notifyOwner gr' $ "You are removed from the " <> gt <> " " <> userGroupReference gr' g <> ".\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (" <> gt <> " owner is removed)." + when (isJust pg_) $ leavePublicGroup g deContactLeftGroup :: ContactId -> GroupInfo -> IO () - deContactLeftGroup ctId g@GroupInfo {groupId} = do + deContactLeftGroup ctId g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "contact ID " <> tshow ctId <> " left group " <> viewGroupName g - -- TODO combine withGroupReg g "contact left" $ \gr -> when (ctId `isOwner` gr) $ setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr' -> do - notifyOwner gr' $ "You left the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner left)." + notifyOwner gr' $ "You left the " <> gt <> " " <> userGroupReference gr' g <> ".\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (" <> gt <> " owner left)." + when (isJust pg_) $ leavePublicGroup g deServiceRemovedFromGroup :: GroupInfo -> IO () - deServiceRemovedFromGroup g@GroupInfo {groupId} = do + deServiceRemovedFromGroup g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "service removed from group " <> viewGroupName g setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr -> do - notifyOwner gr $ serviceName <> " is removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." + notifyOwner gr $ serviceName <> " is removed from the " <> gt <> " " <> userGroupReference gr g <> ".\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (directory service is removed)." deGroupDeleted :: GroupInfo -> IO () - deGroupDeleted g@GroupInfo {groupId} = do + deGroupDeleted g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "group removed " <> viewGroupName g setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr -> do - notifyOwner gr $ "The group " <> userGroupReference gr g <> " is deleted.\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group is deleted)." + notifyOwner gr $ "The " <> gt <> " " <> userGroupReference gr g <> " is deleted.\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (" <> gt <> " is deleted)." + + deChatLinkReceived :: Contact -> MsgChatLink -> Maybe LinkOwnerSig -> IO () + deChatLinkReceived ct (MCLGroup {connLink, groupProfile = GroupProfile {publicGroup = Just PublicGroupProfile {groupType}}}) (Just ownerSig@LinkOwnerSig {ownerId = Just (B64UrlByteString oIdBytes)}) = + case groupType of + GTUnknown tag -> sendMessage cc ct $ "Unsupported group type: " <> T.pack (show tag) + gt -> do + let link = ACL SCMContact $ CLShort connLink + mId = MemberId oIdBytes + gt' = groupTypeStr gt + sendChatCmd cc (APIConnectPlan userId (Just link) True (Just ownerSig)) >>= \case + Right (CRConnectionPlan _ (ACCL SCMContact ccLink) plan) -> + handleGroupLinkPlan ct ccLink mId ownerSig gt' plan + _ -> sendMessage cc ct "Error: could not connect. Please report it to directory admins." + deChatLinkReceived ct (MCLGroup {groupProfile = GroupProfile {publicGroup = Just pg}}) _ = + sendMessage cc ct $ "To add a " <> groupTypeStr' pg <> " to directory you must be the owner." + deChatLinkReceived ct _ _ = + sendMessage cc ct "Only channels can be added to directory via link." + + groupTypeStr :: GroupType -> Text + groupTypeStr = \case + GTChannel -> "channel" + GTGroup -> "group" + GTUnknown _ -> "group" + + groupTypeStr' :: PublicGroupProfile -> Text + groupTypeStr' PublicGroupProfile {groupType} = groupTypeStr groupType + + leavePublicGroup :: GroupInfo -> IO () + leavePublicGroup GroupInfo {groupId} = + void $ sendChatCmd cc (APILeaveGroup groupId) + + handleGroupLinkPlan :: Contact -> CreatedLinkContact -> MemberId -> LinkOwnerSig -> Text -> ConnectionPlan -> IO () + handleGroupLinkPlan ct ccLink mId ownerSig gt = \case + CPGroupLink glp -> case glp of + GLPOk {groupSLinkData_, ownerVerification} -> case (groupSLinkData_, ownerVerification) of + (Just groupSLinkData, Just OVVerified) -> joinAndRegisterPublicGroup ct ccLink mId gt groupSLinkData + (_, Just (OVFailed reason)) -> sendMessage cc ct $ "Link signature verification failed: " <> reason <> ".\nYou must be the " <> gt <> " owner to register it." + (Nothing, _) -> sendMessage cc ct $ "Error: no " <> gt <> " information available via the link." + _ -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership. Please report it to directory admins." + GLPKnown {groupInfo = g, groupUpdated = BoolDef updated, ownerVerification} -> case ownerVerification of + Just OVVerified -> deReregistration ct g updated ownerSig + Just (OVFailed reason) -> sendMessage cc ct $ "Link signature verification failed: " <> reason <> ".\nYou must be the " <> gt <> " owner to register it." + Nothing -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership." + GLPConnectingProhibit _ -> sendMessage cc ct $ "Already connecting to this " <> gt <> "." + GLPConnectingConfirmReconnect -> sendMessage cc ct $ "Already connecting to this " <> gt <> "." + GLPNoRelays _ -> sendMessage cc ct $ T.toTitle gt <> " has no active relays. Please try again later." + GLPOwnLink _ -> sendMessage cc ct "Unexpected error. Please report it to directory admins." + _ -> sendMessage cc ct "Unexpected error. Please report it to directory admins." + + joinAndRegisterPublicGroup :: Contact -> CreatedLinkContact -> MemberId -> Text -> GroupShortLinkData -> IO () + joinAndRegisterPublicGroup ct ccLink mId gt groupSLinkData = do + let GroupShortLinkData {groupProfile = GroupProfile {displayName}} = groupSLinkData + ownerContact = GroupOwnerContact {contactId = contactId' ct, memberId = mId} + sendMessage cc ct $ "Joining the " <> gt <> " " <> displayName <> "…" + sendChatCmd cc (APIPrepareGroup userId ccLink False groupSLinkData) >>= \case + Right (CRNewPreparedChat _ (AChat SCTGroup (Chat (GroupChat gInfo _) _ _))) -> do + let gId = groupId' gInfo + addGroupReg notifyAdminUsers st cc ct gInfo GRSProposed $ \_ -> pure () + sendChatCmd cc (APIConnectPreparedGroup gId False (Just ownerContact) Nothing) >>= \case + Right CRStartedConnectionToGroup {groupInfo = gInfo'} -> + withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (vr cc) user gInfo' mId) >>= \case + Right ownerMember -> + void $ setGroupRegOwner cc gId ownerMember + Left e -> do + logError $ "could not find owner member: " <> T.pack e + sendMessage cc ct "Error: could not find owner member after joining. Please report it to directory admins." + _ -> sendMessage cc ct $ "Error joining " <> gt <> " " <> displayName <> ", please re-send the link!" + _ -> sendMessage cc ct $ "Error joining " <> gt <> " " <> displayName <> ", please re-send the link!" + + deReregistration :: Contact -> GroupInfo -> Bool -> LinkOwnerSig -> IO () + deReregistration ct g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} profileChanged LinkOwnerSig {ownerId = Just (B64UrlByteString oIdBytes)} = do + let mId = MemberId oIdBytes + gt = maybe "group" groupTypeStr' pg_ + withDB "getGroupMemberByMemberId" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (vr cc) user g mId) >>= \case + Right ownerMember@GroupMember {memberRole = role, memberStatus} -> + if + | role >= GROwner && memberStatus /= GSMemUnknown -> + getGroupReg cc groupId >>= \case + Right gr + | contactId' ct `isOwner` gr -> sameOwnerReregistration gr gt + | otherwise -> sendMessage cc ct $ "This " <> gt <> " is registered by another owner." + Left _ -> + addGroupReg notifyAdminUsers st cc ct g (GRSPendingApproval 1) $ \gr -> do + void $ setGroupRegOwner cc groupId ownerMember + sendToApprove g gr 1 + | role < GROwner -> sendMessage cc ct $ "You must be the " <> gt <> " owner to register it." + | otherwise -> sendMessage cc ct $ "Waiting for the owner member to be connected to the " <> gt <> "." + Left _ -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership. Please report it to directory admins." + where + sameOwnerReregistration gr gt = case groupRegStatus gr of + GRSProposed -> sendMessage cc ct $ "Registration is in progress, waiting for the owner member to be connected to the " <> gt <> "." + GRSPendingConfirmation -> pendingApprovalTransition gr gt 1 + GRSPendingUpdate -> pendingApprovalTransition gr gt 1 + GRSPendingApproval n + | profileChanged -> pendingApprovalTransition gr gt $ n + 1 + | otherwise -> sendMessage cc ct $ T.toTitle gt <> " is already pending approval." + GRSActive + | profileChanged -> pendingApprovalTransition gr gt 1 + | otherwise -> sendMessage cc ct $ T.toTitle gt <> " is already listed in the directory." + GRSSuspended -> sendMessage cc ct $ T.toTitle gt <> " is suspended by admin. Please contact support." + GRSSuspendedBadRoles -> pendingApprovalTransition gr gt 1 + GRSRemoved -> pendingApprovalTransition gr gt 1 + pendingApprovalTransition gr gt n = do + let userGroupRef = userGroupReference gr g + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n) $ \gr' -> do + notifyOwner gr' $ + "The " <> gt <> " " <> userGroupRef <> " is submitted for approval.\nIt is hidden from the directory until approved." + sendToApprove g gr' n + deReregistration ct _ _ _ = + sendMessage cc ct "Error: could not verify ownership. Please report it to directory admins." + + deMemberUpdated :: GroupInfo -> GroupMember -> GroupMember -> IO () + deMemberUpdated g@GroupInfo {groupId, groupProfile = GroupProfile {displayName, publicGroup}} fromMember toMember = + withGroupReg g "owner member announced" $ \gr@GroupReg {groupRegStatus, dbOwnerMemberId} -> + when (groupRegStatus == GRSProposed && (dbOwnerMemberId == Just (groupMemberId' fromMember) || dbOwnerMemberId == Just (groupMemberId' toMember))) $ + let GroupMember {memberRole = role} = toMember + gt = maybe "group" groupTypeStr' publicGroup + in if role >= GROwner + then setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval 1) $ \gr' -> do + notifyOwner gr' $ "Joined the " <> gt <> " " <> displayName <> ". Registration is pending approval — it may take up to 48 hours." + sendToApprove g gr' 1 + else do + setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \_ -> pure () + sendMessage' cc (dbContactId gr) "The signing key does not belong to a current owner. Registration cancelled." deUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO () deUserCommand ct ciId = \case DCHelp DHSRegistration -> sendMessage cc ct $ - "You must be the group owner to add it to the directory:\n\n\ - \1️⃣ *Invite* " + "You must be the group or channel owner to add it to the directory.\n\n\ + \*To register a channel*, use _Share via chat_ to send its link to " + <> serviceName + <> " bot.\n\n\ + \*To register a group*:\n\ + \1️⃣ *Invite* " <> serviceName <> " bot to your group as *admin* - it will create a link for new members to join.\n\ - \2️⃣ *Add* this link to the group's welcome message.\n\ - \3️⃣ We *review* your group. Once *approved*, anybody can find it.\n\n\ - \_We usually approve within a day, except holidays_. [More details](https://simplex.chat/docs/directory.html#adding-groups-to-the-directory)." + \2️⃣ *Add* this link to the group's welcome message.\n\n\ + \Once your group or channel *approved*, it can be found here or at [simplex.chat/directory](https://simplex.chat/directory).\n\n\ + \_We usually review within a day, except holidays_. [More details](https://simplex.chat/docs/directory.html#adding-groups-to-the-directory)." DCHelp DHSCommands -> sendMessage cc ct $ "/'help commands' - receive this help message.\n\ - \/help - how to register your group to be added to directory.\n\ + \/help - how to register your group or channel to be added to directory.\n\ \/list - list the groups you registered.\n\ \`/role ` - view and set default member role for your group.\n\ \`/filter ` - view and set spam filter settings for group.\n\ \`/link ` - view and upgrade group link.\n\ \`/delete :` - remove the group you submitted from directory, with _ID_ and _name_ as shown by /list command.\n\n\ \To search for groups, send the search text." - DCSearchGroup s -> - sendFoundListedGroups (STSearch s) Nothing "No groups found" $ \gs n -> -- $ sendSearchResults s + DCSearchGroup s ft -> + sendFoundListedGroups (STSearch s) Nothing notFound $ \gs n -> let more = if n > length gs then ", sending top " <> tshow (length gs) else "" in "Found " <> tshow n <> " group(s)" <> more <> "." + where + notFound + | hasSimplexGroupLink ft = "No groups found.\nTo register a group or a channel, please use \"Share via chat\" feature." + | otherwise = "No groups found" + hasSimplexGroupLink = \case + Just fts -> any isGroupLink fts + Nothing -> False + isGroupLink (FormattedText (Just SimplexLink {linkType}) _) = linkType == XLGroup || linkType == XLChannel + isGroupLink _ = False DCSearchNext -> atomically (TM.lookup (contactId' ct) searchRequests) >>= \case Just SearchRequest {searchType, searchTime, lastGroup} -> do @@ -858,14 +1117,17 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Left e -> sendReply $ "Error reading groups: " <> T.pack e Right gs -> sendGroupsInfo ct ciId isAdmin (gs, length gs) DCDeleteGroup gId gName -> - (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ \GroupInfo {groupProfile = GroupProfile {displayName}} GroupReg {dbGroupId} -> do + (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ \g@GroupInfo {groupProfile = GroupProfile {displayName, publicGroup = pg_}} GroupReg {dbGroupId} -> do + let gt = maybe "group" groupTypeStr' pg_ delGroupReg cc dbGroupId >>= \case Right () -> do logGDelete st dbGroupId - sendReply $ (if isAdmin then "The group " else "Your group ") <> displayName <> " is deleted from the directory" - Left e -> sendReply $ "Error deleting group " <> displayName <> ": " <> T.pack e + sendReply $ (if isAdmin then "The " <> gt <> " " else "Your " <> gt <> " ") <> displayName <> " is deleted from the directory" + when (isJust pg_) $ leavePublicGroup g + Left e -> sendReply $ "Error deleting " <> gt <> " " <> displayName <> ": " <> T.pack e DCMemberRole gId gName_ mRole_ -> - (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> + ifPublicGroup g (sendReply "This command is not available for public groups.") $ do let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g case mRole_ of Nothing -> @@ -885,7 +1147,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName initialRole n mRole = "The initial member role for the group " <> n <> " is set to *" <> textEncode mRole <> "*\n" onlyViaLink gLink = "*Please note*: it applies only to members joining via this link: " <> groupLinkText gLink DCGroupFilter gId gName_ acceptance_ -> - (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> + ifPublicGroup g (sendReply "This command is not available for public groups.") $ do let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g a = groupMemberAcceptance g case acceptance_ of @@ -916,39 +1179,42 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Just PCAll -> "_enabled_" Just PCNoImage -> "_enabled for profiles without image_" DCShowUpgradeGroupLink gId gName_ -> - (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \GroupInfo {groupId, localDisplayName = gName} _ -> do - let groupRef = groupReference' gId gName - withGroupLinkResult groupRef (sendChatCmd cc $ APIGetGroupLink groupId) $ - \GroupLink {connLinkContact = gLink@(CCLink _ sLnk_), acceptMemberRole, shortLinkDataSet, shortLinkLargeDataSet = BoolDef slLargeDataSet} -> do - let shouldBeUpgraded = isNothing sLnk_ || not shortLinkDataSet || not slLargeDataSet - sendReply $ - T.unlines $ - [ "The link to join the group " <> groupRef <> ":", - groupLinkText gLink, - "New member role: " <> textEncode acceptMemberRole - ] - <> ["The link is being upgraded..." | shouldBeUpgraded] - when shouldBeUpgraded $ do - let send = sendComposedMessage cc ct Nothing . MCText . T.unlines - withGroupLinkResult groupRef (sendChatCmd cc $ APIAddGroupShortLink groupId) $ - \GroupLink {connLinkContact = CCLink _ sLnk_'} -> case (sLnk_, sLnk_') of - (Just _, Just _) -> - send ["The group link is upgraded for: " <> groupRef, "No changes to group needed."] - (Nothing, Just sLnk) -> - sendComposedMessages - cc - (SRDirect $ contactId' ct) - [ MCText $ - T.unlines - [ "Please replace the old link in welcome message of your group " <> groupRef, - "If this is the only change, the group will remain listed in directory without re-approval.", - "", - "The new link:" - ], - MCText $ strEncodeTxt sLnk - ] - (_, Nothing) -> - send ["The short link is not created for " <> groupRef, "Please report it to the developers."] + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}, localDisplayName = gName} _ -> case pg_ of + Just pg@PublicGroupProfile {groupLink} -> + sendReply $ "The link to join the " <> groupTypeStr' pg <> " " <> groupReference' gId gName <> ":\n" <> strEncodeTxt groupLink + Nothing -> do + let groupRef = groupReference' gId gName + withGroupLinkResult groupRef (sendChatCmd cc $ APIGetGroupLink groupId) $ + \GroupLink {connLinkContact = gLink@(CCLink _ sLnk_), acceptMemberRole, shortLinkDataSet, shortLinkLargeDataSet = BoolDef slLargeDataSet} -> do + let shouldBeUpgraded = isNothing sLnk_ || not shortLinkDataSet || not slLargeDataSet + sendReply $ + T.unlines $ + [ "The link to join the group " <> groupRef <> ":", + groupLinkText gLink, + "New member role: " <> textEncode acceptMemberRole + ] + <> ["The link is being upgraded..." | shouldBeUpgraded] + when shouldBeUpgraded $ do + let send = sendComposedMessage cc ct Nothing . MCText . T.unlines + withGroupLinkResult groupRef (sendChatCmd cc $ APIAddGroupShortLink groupId) $ + \GroupLink {connLinkContact = CCLink _ sLnk_'} -> case (sLnk_, sLnk_') of + (Just _, Just _) -> + send ["The group link is upgraded for: " <> groupRef, "No changes to group needed."] + (Nothing, Just sLnk) -> + sendComposedMessages + cc + (SRDirect $ contactId' ct) + [ MCText $ + T.unlines + [ "Please replace the old link in welcome message of your group " <> groupRef, + "If this is the only change, the group will remain listed in directory without re-approval.", + "", + "The new link:" + ], + MCText $ strEncodeTxt sLnk + ] + (_, Nothing) -> + send ["The short link is not created for " <> groupRef, "Please report it to the developers."] where withGroupLinkResult groupRef a cb = a >>= \case @@ -1000,8 +1266,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName where msgs = replyMsg :| map foundGroup gs <> [moreMsg | moreGroups > 0] replyMsg = (Just ciId, MCText reply) - foundGroup (GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_, memberAdmission}, groupSummary = GroupSummary {currentMembers}}, _) = - let membersStr = "_" <> tshow currentMembers <> " members_" + foundGroup (GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_, memberAdmission}, groupSummary}, _) = + let membersStr = "_" <> membersCountStr p groupSummary <> "_" showId = if isAdmin then tshow groupId <> ". " else "" text = T.unlines $ [showId <> groupInfoText p, membersStr] ++ knockingStr memberAdmission in (Nothing, maybe (MCText text) (\image -> MCImage {text, image}) image_) @@ -1014,40 +1280,49 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName withGroupAndReg sendReply groupId n $ \g gr@GroupReg {userGroupRegId = ugrId, promoted} -> case groupRegStatus gr of GRSPendingApproval gaId - | gaId == groupApprovalId -> + | gaId == groupApprovalId -> do + let GroupInfo {groupProfile = GroupProfile {publicGroup = pg_}} = g + isPublicGroup_ = isJust pg_ + gt = maybe "group" groupTypeStr' pg_ getDuplicateGroup g >>= \case Left e -> sendReply $ "Error: getDuplicateGroup. Please notify the developers.\n" <> T.pack e - Right DGReserved -> sendReply $ "The group " <> groupRef <> " is already listed in the directory." - _ -> getGroupRolesStatus g gr >>= \case - Right GRSOk -> do - let grPromoted' - | promoted || knownCt `elem` superUsers = fromMaybe promoted promote - | otherwise = False - setGroupStatusPromo sendReply st env cc gr GRSActive grPromoted' $ do - let approved = "The group " <> userGroupReference' gr n <> " is approved" - notifyOwner gr $ - (approved <> " and listed in directory - please moderate it!\n") - <> "_Please note_: if you change the group profile it will be hidden from directory until it is re-approved.\n\n" - <> "Supported commands:\n" - <> ("/'filter " <> tshow ugrId <> "' - to configure anti-spam filter.\n") - <> ("/'role " <> tshow ugrId <> "' - to set default member role.\n") - <> ("/'link " <> tshow ugrId <> "' - to view/upgrade group link.") - invited <- - forM ownersGroup $ \og@KnownGroup {localDisplayName = ogName} -> do - inviteToOwnersGroup og gr $ \case - Right () -> do - owner <- groupOwnerInfo groupRef $ dbContactId gr - pure $ "Invited " <> owner <> " to owners' group " <> viewName ogName - Left err -> pure err - sendReply $ "Group approved" <> (if grPromoted' then " (promoted)" else "") <> "!" <> maybe "" ("\n" <>) invited - notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) <> maybe "" ("\n" <>) invited - Right GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin - Right GRSContactNotOwner -> replyNotApproved "user is not an owner." - Right GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin - Left e -> sendReply $ "Error: getGroupRolesStatus. Please notify the developers.\n" <> T.pack e - where - replyNotApproved reason = sendReply $ "Group is not approved: " <> reason - serviceNotAdmin = serviceName <> " is not an admin." + Right DGReserved -> sendReply $ "The " <> gt <> " " <> groupRef <> " is already listed in the directory." + _ -> do + rolesOk <- if isPublicGroup_ then pure (Right GRSOk) else getGroupRolesStatus g gr + case rolesOk of + Right GRSOk -> do + let grPromoted' + | promoted || knownCt `elem` superUsers = fromMaybe promoted promote + | otherwise = False + setGroupStatusPromo sendReply st env cc gr GRSActive grPromoted' $ do + let approved = "The " <> gt <> " " <> userGroupReference' gr n <> " is approved" + let commands + | isPublicGroup_ = "" + | otherwise = + "\n\nSupported commands:\n" + <> ("/'filter " <> tshow ugrId <> "' - to configure anti-spam filter.\n") + <> ("/'role " <> tshow ugrId <> "' - to set default member role.\n") + <> ("/'link " <> tshow ugrId <> "' - to view/upgrade group link.") + notifyOwner gr $ + (approved <> " and listed in directory - please moderate it!\n") + <> "_Please note_: if you change the " <> gt <> " profile it will be hidden from directory until it is re-approved." + <> commands + invited <- + forM ownersGroup $ \og@KnownGroup {localDisplayName = ogName} -> do + inviteToOwnersGroup og gr $ \case + Right () -> do + owner <- groupOwnerInfo groupRef $ dbContactId gr + pure $ "Invited " <> owner <> " to owners' group " <> viewName ogName + Left err -> pure err + sendReply $ T.toTitle gt <> " approved" <> (if grPromoted' then " (promoted)" else "") <> "!" <> maybe "" ("\n" <>) invited + notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) <> maybe "" ("\n" <>) invited + Right GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin + Right GRSContactNotOwner -> replyNotApproved "user is not an owner." + Right GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin + Left e -> sendReply $ "Error: getGroupRolesStatus. Please notify the developers.\n" <> T.pack e + where + replyNotApproved reason = sendReply $ "Group is not approved: " <> reason + serviceNotAdmin = serviceName <> " is not an admin." | otherwise -> sendReply "Incorrect approval code" _ -> sendReply $ "Error: the group " <> groupRef <> " is not pending approval." where @@ -1120,7 +1395,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName r -> contErr r r -> contErr r where - alreadyMember = isJust . find ((Just ctId ==) . memberContactId) + alreadyMember = any (\m -> memberContactId m == Just ctId && memberCurrent m) contErr r = do let err = "error inviting contact ID " <> tshow ctId <> " to owners' group: " <> tshow r putStrLn $ T.unpack err @@ -1189,7 +1464,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName GroupReg {userGroupRegId, groupRegStatus} = gr useGroupId = if isAdmin then groupId else userGroupRegId statusStr = "Status: " <> groupRegStatusText groupRegStatus - membersStr = "_" <> tshow (currentMembers groupSummary) <> " members_" + membersStr = "_" <> membersCountStr p groupSummary <> "_" cmds = "/'role " <> tshow useGroupId <> "', /'filter " <> tshow useGroupId <> "'" ownerStr = maybe "" (("Owner: " <>) . either (("getContact error: " <>) . T.pack) localDisplayName') ct_ text = T.unlines $ [tshow useGroupId <> ". " <> groupInfoText p] ++ [ownerStr | isAdmin] ++ [membersStr, statusStr] ++ knockingStr memberAdmission ++ [cmds] @@ -1203,7 +1478,7 @@ setGroupStatusPromo sendReply st env cc GroupReg {dbGroupId = gId} grStatus' grP Left e -> sendReply $ "Error updating group " <> tshow gId <> " status: " <> T.pack e Right (status, grPromoted) -> do when ((status == DSListed || status' == DSListed) && (status /= status' || grPromoted /= grPromoted')) $ - listingsUpdated env cc + listingsUpdated env logGUpdateStatus st gId grStatus' logGUpdatePromotion st gId grPromoted' continue @@ -1223,7 +1498,7 @@ setGroupStatus sendMsg st env cc gId grStatus' continue = do Left e -> sendMsg $ "Error updating group " <> tshow gId <> " status: " <> T.pack e Right (grStatus, gr) -> do let status = grDirectoryStatus grStatus - when ((status == DSListed || status' == DSListed) && status /= status') $ listingsUpdated env cc + when ((status == DSListed || status' == DSListed) && status /= status') $ listingsUpdated env logGUpdateStatus st gId grStatus' continue gr @@ -1232,7 +1507,7 @@ setGroupPromoted sendReply st env cc GroupReg {dbGroupId = gId} grPromoted' cont setGroupPromotedStore cc gId grPromoted' >>= \case Left e -> sendReply $ "Error updating group " <> tshow gId <> " status: " <> T.pack e Right (status, grPromoted) -> do - when (status == DSListed && grPromoted' /= grPromoted) $ listingsUpdated env cc + when (status == DSListed && grPromoted' /= grPromoted) $ listingsUpdated env logGUpdatePromotion st gId grPromoted' continue diff --git a/apps/simplex-support-bot/.gitignore b/apps/simplex-support-bot/.gitignore new file mode 100644 index 0000000000..9f77d70eda --- /dev/null +++ b/apps/simplex-support-bot/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +data/ +.env diff --git a/apps/simplex-support-bot/README.md b/apps/simplex-support-bot/README.md new file mode 100644 index 0000000000..19b9ab8bd6 --- /dev/null +++ b/apps/simplex-support-bot/README.md @@ -0,0 +1,101 @@ +# SimpleX Support Bot + +A business-address bot that triages incoming support chats, optionally runs them through Grok, and routes handoffs to a team group. + +## Prerequisites + +- Node.js v18 or newer (v24 tested) +- `GROK_API_KEY` env var (xAI) — optional; the bot runs without it +- For the PostgreSQL backend: Linux x86_64, `libpq5` installed on the host, and a reachable PostgreSQL server + +## Install & build + +```bash +cd apps/simplex-support-bot +npm install # downloads native libs + transitive deps +npm run build # tsc +``` + +By default this installs the **SQLite** backend. + +To use **PostgreSQL** instead, drop a `.npmrc` next to `package.json` *before* `npm install`: + +```bash +echo 'simplex_backend=postgres' > .npmrc +npm install # now pulls postgres-flavored native libs +npm run build +``` + +`.npmrc` lives next to the package — npm reads it natively, no extra setup. + +### Switching backends + +`npm install` is a no-op for already-installed deps, so editing `.npmrc` and re-running `npm install` will *not* re-trigger `simplex-chat`'s preinstall. To switch backends, force a clean install: + +```bash +rm -rf node_modules +npm install # download-libs.js re-runs and pulls the right native lib +``` + +## Run + +```bash +mkdir -p data # state file lives here by default + +# SQLite (default) +npm start -- --team-group "Support Team" + +# PostgreSQL +npm start -- --team-group "Support Team" \ + --pg-conn "postgres://user:pass@host/db" +``` + +The bot runs via `npm start` so npm can expose `.npmrc` settings to the process — `detectBackend()` reads `npm_config_simplex_backend` to know which backend was installed. + +## Flags + +Run `npm start -- --help` for the auto-generated reference. Summary: + +| Flag | Backend | Required | Default | Description | +|---|---|---|---|---| +| `--team-group` | both | yes | — | team group display name | +| `--state-file` | both | no | `./data/state.json` | path to bot state JSON | +| `--sqlite-file-prefix` | sqlite | no | `./data/simplex` | DB file prefix (creates `_chat.db`, `_agent.db`) | +| `--sqlite-key` | sqlite | no | (unencrypted) | SQLCipher encryption key | +| `--pg-conn` | postgres | yes | — | PostgreSQL connection string | +| `--pg-schema` | postgres | no | `simplex_v1` | schema prefix used for bot tables | +| `-a` / `--auto-add-team-members` | both | no | | comma-separated `ID:name` pairs (e.g. `1:Alice,2:Bob`) | +| `--timezone` | both | no | `UTC` | IANA zone for weekend detection | +| `--complete-hours` | both | no | `3` | auto-complete chats after N hours idle (`0` disables) | +| `--card-flush-seconds` | both | no | `300` | debounce card state writes | +| `--context-file` | both | required with `GROK_API_KEY` | | text file with Grok system context | +| `-h` / `--help` | both | no | | show usage and exit | + +## Environment variables + +| Var | Purpose | +|---|---| +| `GROK_API_KEY` | xAI API key; enables Grok replies | +| `SIMPLEX_BACKEND` | alternative to `.npmrc` for selecting the install backend (`sqlite` or `postgres`) | + +## Local development against unreleased lib changes + +This package depends on `simplex-chat` from npm. To test against an in-tree version: + +```bash +# In packages/simplex-chat-nodejs +npm link + +# In apps/simplex-support-bot +npm link simplex-chat +``` + +`npm unlink simplex-chat && npm install` reverts to the registry version. + +## Troubleshooting + +- **`--pg-conn is required when backend is postgres`** — the postgres backend is installed but you didn't pass a connection string. +- **`libpq5` errors at startup** — install `libpq5` on the host (`apt install libpq5` on Debian/Ubuntu). +- **`ENOENT: no such file or directory, open './data/state.json'`** — the parent directory of `--state-file` must exist; `mkdir -p data` before starting. +- **Wrong backend installed** — check `node_modules/simplex-chat/libs/installed.txt`. Edit `.npmrc`, then `rm -rf node_modules && npm install` to switch (`npm install` alone won't re-run the dep's preinstall). +- **`libpq` connection error** at startup with sqlite-flavored config (or vice versa) — `.npmrc` was changed but libs weren't reinstalled. See "Switching backends" above. diff --git a/apps/simplex-support-bot/bot.test.ts b/apps/simplex-support-bot/bot.test.ts new file mode 100644 index 0000000000..d390835f46 --- /dev/null +++ b/apps/simplex-support-bot/bot.test.ts @@ -0,0 +1,2481 @@ +import {describe, test, expect, beforeEach, vi} from "vitest" +import {SupportBot} from "./src/bot.js" +import {CardManager} from "./src/cards.js" +import {parseConfig} from "./src/config.js" +import {GrokApiClient} from "./src/grok.js" +import {welcomeMessage, queueMessage, grokActivatedMessage, teamLockedMessage} from "./src/messages.js" + +// Silence console output during tests +vi.spyOn(console, "log").mockImplementation(() => {}) +vi.spyOn(console, "error").mockImplementation(() => {}) + +// ─── Type stubs ─── + +const ChatType = {Direct: "direct" as const, Group: "group" as const, Local: "local" as const} +const GroupMemberRole = {Member: "member" as const, Owner: "owner" as const, Admin: "admin" as const} +const GroupMemberStatus = {Connected: "connected" as const, Complete: "complete" as const, Announced: "announced" as const, Left: "left" as const} +const GroupFeatureEnabled = {On: "on" as const, Off: "off" as const} +const CIDeleteMode = {Broadcast: "broadcast" as const} + +// ─── Mock infrastructure ─── + +let nextItemId = 1000 + +class MockChatApi { + sent: {chat: [string, number]; text: string}[] = [] + added: {groupId: number; contactId: number; role: string}[] = [] + removed: {groupId: number; memberIds: number[]}[] = [] + joined: number[] = [] + deleted: {chatType: string; chatId: number; itemIds: number[]; mode: string}[] = [] + customData = new Map() + roleChanges: {groupId: number; memberIds: number[]; role: string}[] = [] + profileUpdates: {groupId: number; profile: any}[] = [] + + members = new Map() + chatItems = new Map() + groups = new Map() + activeUserId = 1 + + private _addMemberFails = false + private _addMemberError: any = null + private _deleteChatItemsFails = false + + apiAddMemberWillFail(err?: any) { this._addMemberFails = true; this._addMemberError = err } + apiDeleteChatItemsWillFail() { this._deleteChatItemsFails = true } + + async apiSetActiveUser(userId: number) { this.activeUserId = userId; return {userId, profile: {displayName: "test"}} } + async apiSendMessages(chatRef: any, messages: any[]) { + // Normalize chat ref: accept both [type, id] tuples and {chatType, chatId} objects + const chat: [string, number] = Array.isArray(chatRef) + ? chatRef + : [chatRef.chatType, chatRef.chatId] + return messages.map(msg => { + const text = msg.msgContent?.text || "" + this.sent.push({chat, text}) + const itemId = nextItemId++ + return {chatItem: {meta: {itemId}, chatDir: {type: "groupSnd"}, content: {type: "sndMsgContent", msgContent: {type: "text", text}}}} + }) + } + async apiSendTextMessage(chat: [string, number], text: string) { + return this.apiSendMessages(chat, [{msgContent: {type: "text", text}, mentions: {}}]) + } + async apiAddMember(groupId: number, contactId: number, role: string) { + if (this._addMemberFails) { + this._addMemberFails = false + throw this._addMemberError || new Error("apiAddMember failed") + } + this.added.push({groupId, contactId, role}) + const memberId = `member-${contactId}` + const groupMemberId = 5000 + contactId + return {memberId, groupMemberId, memberContactId: contactId, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: `Contact${contactId}`}} + } + async apiRemoveMembers(groupId: number, memberIds: number[]) { + this.removed.push({groupId, memberIds}) + return memberIds.map(id => ({groupMemberId: id})) + } + async apiJoinGroup(groupId: number) { + this.joined.push(groupId) + return {groupId} + } + async apiSetMembersRole(groupId: number, memberIds: number[], role: string) { + this.roleChanges.push({groupId, memberIds, role}) + } + async apiListMembers(groupId: number) { + return this.members.get(groupId) || [] + } + async apiGetChat(_chatType: string, chatId: number, _count: number) { + const items = this.chatItems.get(chatId) || [] + const groupInfo = this.groups.get(chatId) + return { + chatInfo: {type: "group", groupInfo: groupInfo || makeGroupInfo(chatId)}, + chatItems: items, + chatStats: {unreadCount: 0, unreadMentions: 0, reportsCount: 0, minUnreadItemId: 0, unreadChat: false}, + } + } + async apiListGroups(_userId: number) { + return [...this.groups.values()].map(g => ({...g, customData: this.customData.get(g.groupId)})) + } + async apiSetGroupCustomData(groupId: number, data?: any) { + if (data === undefined) this.customData.delete(groupId) + else this.customData.set(groupId, data) + } + async apiDeleteChatItems(chatType: string, chatId: number, itemIds: number[], mode: string) { + if (this._deleteChatItemsFails) { + this._deleteChatItemsFails = false + throw new Error("apiDeleteChatItems failed") + } + this.deleted.push({chatType, chatId, itemIds, mode}) + return [] + } + async apiUpdateGroupProfile(groupId: number, profile: any) { + this.profileUpdates.push({groupId, profile}) + return this.groups.get(groupId) || makeGroupInfo(groupId) + } + + memberContacts: {groupId: number; groupMemberId: number; contactId: number}[] = [] + memberContactInvitations: {contactId: number; text: string}[] = [] + + async apiCreateMemberContact(groupId: number, groupMemberId: number): Promise { + const contactId = nextItemId++ + this.memberContacts.push({groupId, groupMemberId, contactId}) + return {contactId, profile: {displayName: "member"}} + } + async apiSendMemberContactInvitation(contactId: number, message?: any): Promise { + const text = typeof message === "string" ? message : (message?.text ?? "") + this.memberContactInvitations.push({contactId, text}) + this.sent.push({chat: [ChatType.Direct, contactId], text}) + return {contactId, profile: {displayName: "member"}} + } + + rawCmds: string[] = [] + async sendChatCmd(cmd: string) { + this.rawCmds.push(cmd) + return {type: "cmdOk"} + } + + sentTo(groupId: number): string[] { + return this.sent.filter(s => s.chat[0] === ChatType.Group && s.chat[1] === groupId).map(s => s.text) + } + lastSentTo(groupId: number): string | undefined { + const msgs = this.sentTo(groupId) + return msgs[msgs.length - 1] + } + sentDirect(contactId: number): string[] { + return this.sent.filter(s => s.chat[0] === ChatType.Direct && s.chat[1] === contactId).map(s => s.text) + } +} + +class MockGrokApi { + calls: {history: any[]; message: string}[] = [] + private _response = "Grok answer" + private _willFail = false + private _gate: {promise: Promise; release: () => void} | null = null + + willRespond(text: string) { this._response = text; this._willFail = false } + willFail() { this._willFail = true } + + // Block every subsequent chat() call until releaseChat() is invoked. Used to + // observe in-flight concurrency without relying on wall-clock timing. + blockChat() { + let release!: () => void + const promise = new Promise(r => { release = r }) + this._gate = {promise, release} + } + releaseChat() { + this._gate?.release() + this._gate = null + } + + async chat(history: any[], userMessage: string): Promise { + this.calls.push({history, message: userMessage}) + if (this._gate) await this._gate.promise + if (this._willFail) { this._willFail = false; throw new Error("Grok API error") } + return this._response + } +} + +// ─── Factory helpers ─── + +const MAIN_USER_ID = 1 +const GROK_USER_ID = 2 +const TEAM_GROUP_ID = 50 +const CUSTOMER_GROUP_ID = 100 +const GROK_CONTACT_ID = 10 +const TEAM_MEMBER_1_ID = 20 +const TEAM_MEMBER_2_ID = 21 +const GROK_LOCAL_GROUP_ID = 200 +const CUSTOMER_ID = "customer-1" + +// Commands passed into SupportBot; matches what index.ts constructs when +// Grok is enabled. Tests that disable grokApi still pass the full list +// because the ctor doesn't care; the value is pushed to a group's +// groupPreferences on the first sendToGroup() call. +const DESIRED_COMMANDS = [ + {type: "command" as const, keyword: "grok", label: "Ask Grok"}, + {type: "command" as const, keyword: "team", label: "Switch to team"}, +] + +// ─── Member factories ─── + +function makeTeamMember(contactId: number, name = `Contact${contactId}`, groupMemberId?: number) { + return { + memberId: `team-${contactId}`, + groupMemberId: groupMemberId ?? 5000 + contactId, + memberContactId: contactId, + memberStatus: GroupMemberStatus.Connected, + memberProfile: {displayName: name}, + } +} + +function makeGrokMember(groupMemberId = 7777) { + return { + memberId: "grok-member", + groupMemberId, + memberContactId: GROK_CONTACT_ID, + memberStatus: GroupMemberStatus.Connected, + memberProfile: {displayName: "Grok"}, + } +} + +function makeCustomerMember(status = GroupMemberStatus.Connected) { + return { + memberId: CUSTOMER_ID, + groupMemberId: 3000, + memberStatus: status, + memberProfile: {displayName: "Customer"}, + } +} + +function makeConfig(overrides: Partial = {}) { + return { + stateFile: "./test-data/state.json", + db: {type: "sqlite", filePrefix: "./test-data/simplex"}, + teamGroup: {id: TEAM_GROUP_ID, name: "SupportTeam"}, + teamMembers: [ + {id: TEAM_MEMBER_1_ID, name: "Alice"}, + {id: TEAM_MEMBER_2_ID, name: "Bob"}, + ], + groupLinks: "", + timezone: "UTC", + completeHours: 3, + cardFlushSeconds: 300, + grokApiKey: "test-key", + grokContactId: GROK_CONTACT_ID as number | null, + ...overrides, + } +} + +function makeGroupInfo(groupId: number, opts: Partial = {}): any { + return { + groupId, + groupProfile: {displayName: opts.displayName || `Group${groupId}`, fullName: ""}, + businessChat: opts.businessChat !== undefined ? opts.businessChat : { + chatType: "business", + businessId: "bot-1", + customerId: opts.customerId || CUSTOMER_ID, + }, + membership: {memberId: "bot-member"}, + customData: opts.customData, + chatSettings: {enableNtfs: "all", favorite: false}, + fullGroupPreferences: {}, + localDisplayName: `group-${groupId}`, + localAlias: "", + useRelays: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + chatTags: [], + groupSummary: {}, + membersRequireAttention: 0, + } +} + +function makeUser(userId: number) { + return {userId, profile: {displayName: userId === MAIN_USER_ID ? "Ask SimpleX Team" : "Grok"}} +} + +function makeChatItem(opts: { + dir: "groupSnd" | "groupRcv" | "directRcv" + text?: string + memberId?: string + memberContactId?: number + memberDisplayName?: string + msgType?: string + groupId?: number +}): any { + const itemId = nextItemId++ + const now = new Date().toISOString() + const msgContent = opts.msgType + ? {type: opts.msgType, text: opts.text || ""} + : {type: "text", text: opts.text || ""} + + let chatDir: any + if (opts.dir === "groupSnd") { + chatDir = {type: "groupSnd"} + } else if (opts.dir === "groupRcv") { + chatDir = { + type: "groupRcv", + groupMember: { + memberId: opts.memberId || CUSTOMER_ID, + groupMemberId: 3000, + memberContactId: opts.memberContactId, + memberStatus: GroupMemberStatus.Connected, + memberProfile: {displayName: opts.memberDisplayName || "Customer"}, + }, + } + } else { + chatDir = {type: "directRcv"} + } + + return { + chatDir, + meta: {itemId, itemTs: now, createdAt: now, itemText: opts.text || "", itemStatus: {type: "sndSent"}, itemEdited: false}, + content: {type: opts.dir === "groupSnd" ? "sndMsgContent" : "rcvMsgContent", msgContent}, + mentions: {}, + reactions: [], + } +} + +function makeAChatItem(chatItem: any, groupId = CUSTOMER_GROUP_ID): any { + return { + chatInfo: {type: "group", groupInfo: makeGroupInfo(groupId)}, + chatItem, + } +} + +function makeDirectAChatItem(chatItem: any, contactId: number): any { + return { + chatInfo: {type: "direct", contact: {contactId, profile: {displayName: "Someone"}}}, + chatItem, + } +} + +// ─── Shared test state ─── + +let chat: MockChatApi +let grokApi: MockGrokApi +let config: ReturnType +let bot: InstanceType +let cards: InstanceType + +// ─── Setup and helpers ─── + +function setup(configOverrides: Partial = {}) { + nextItemId = 1000 + chat = new MockChatApi() + grokApi = new MockGrokApi() + config = makeConfig(configOverrides) + + // Register team group and customer group in mock + const teamGroupInfo = makeGroupInfo(TEAM_GROUP_ID, {businessChat: null, displayName: "SupportTeam"}) + chat.groups.set(TEAM_GROUP_ID, teamGroupInfo) + chat.groups.set(CUSTOMER_GROUP_ID, makeGroupInfo(CUSTOMER_GROUP_ID)) + + cards = new CardManager(chat as any, config as any, MAIN_USER_ID, 999999999) + bot = new SupportBot(chat as any, grokApi as any, config as any, MAIN_USER_ID, GROK_USER_ID, DESIRED_COMMANDS) + // Replace cards with our constructed one that has a long flush interval + bot.cards = cards +} + +function customerMessage(text: string, groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: CUSTOMER_ID}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function customerNonTextMessage(groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text: "", memberId: CUSTOMER_ID, msgType: "image"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function teamMemberMessage(text: string, contactId = TEAM_MEMBER_1_ID, groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: `team-${contactId}`, memberContactId: contactId, memberDisplayName: "Alice"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function grokResponseMessage(text: string, groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: "grok-member", memberContactId: GROK_CONTACT_ID, memberDisplayName: "Grok"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function directMessage(text: string, contactId: number): any { + const ci = makeChatItem({dir: "directRcv", text}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeDirectAChatItem(ci, contactId)], + } +} + +function teamGroupMessage(text: string, senderContactId = TEAM_MEMBER_1_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: `team-${senderContactId}`, memberContactId: senderContactId, memberDisplayName: "Alice"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(TEAM_GROUP_ID, {businessChat: null})}, chatItem: ci}], + } +} + +// Simulate bot sending a message to the customer group (adds it to chatItems history) +function addBotMessage(text: string, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupSnd", text}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +function addCustomerMessageToHistory(text: string, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupRcv", text, memberId: CUSTOMER_ID}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +function addTeamMemberMessageToHistory(text: string, contactId = TEAM_MEMBER_1_ID, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupRcv", text, memberId: `team-${contactId}`, memberContactId: contactId}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +function addGrokMessageToHistory(text: string, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupRcv", text, memberId: "grok-member", memberContactId: GROK_CONTACT_ID}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +// State helpers — reach specific states +async function reachQueue(groupId = CUSTOMER_GROUP_ID) { + await bot.onNewChatItems(customerMessage("Hello, I need help", groupId)) + // This should have sent queue message + created card +} + +async function reachGrok(groupId = CUSTOMER_GROUP_ID) { + await reachQueue(groupId) + // Add the queue message to history so state derivation sees it + addBotMessage("The team will reply to your message", groupId) + + // Send /grok command. This triggers activateGrok which needs the join flow. + // We need to simulate Grok join success. + const grokJoinPromise = simulateGrokJoinSuccess(groupId) + await bot.onNewChatItems(customerMessage("/grok", groupId)) + await grokJoinPromise +} + +async function simulateGrokJoinSuccess(mainGroupId = CUSTOMER_GROUP_ID) { + // Wait for apiAddMember to be called, then simulate Grok invitation + join + await new Promise(r => setTimeout(r, 10)) + // Find the pending grok join via the added members + const addedGrok = chat.added.find(a => a.contactId === GROK_CONTACT_ID && a.groupId === mainGroupId) + if (!addedGrok) return + + // Simulate Grok receivedGroupInvitation + const memberId = `member-${GROK_CONTACT_ID}` + await bot.onGrokGroupInvitation({ + type: "receivedGroupInvitation", + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + }) + + // Simulate Grok connectedToGroupMember + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999, memberContactId: undefined}, + }) +} + +async function reachTeamPending(groupId = CUSTOMER_GROUP_ID) { + await reachQueue(groupId) + addBotMessage("The team will reply to your message", groupId) + await bot.onNewChatItems(customerMessage("/team", groupId)) +} + +async function reachTeam(groupId = CUSTOMER_GROUP_ID) { + await reachTeamPending(groupId) + addBotMessage("We will reply within 24 hours.", groupId) + chat.members.set(groupId, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member sends a text message (triggers one-way gate) + addTeamMemberMessageToHistory("Hi, how can I help?", TEAM_MEMBER_1_ID, groupId) + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?", TEAM_MEMBER_1_ID, groupId)) +} + +// ─── Assertion helpers ─── + +function expectSentToGroup(groupId: number, substring: string) { + const msgs = chat.sentTo(groupId) + expect(msgs.some(m => m.includes(substring)), + `Expected message containing "${substring}" sent to group ${groupId}, got:\n${msgs.join("\n")}` + ).toBe(true) +} + +function expectNotSentToGroup(groupId: number, substring: string) { + expect(chat.sentTo(groupId).every(m => !m.includes(substring))).toBe(true) +} + +function expectDmSent(contactId: number, substring: string) { + expect(chat.sentDirect(contactId).some(m => m.includes(substring))).toBe(true) +} + +function expectAnySent(substring: string) { + expect(chat.sent.some(s => s.text.includes(substring))).toBe(true) +} + +function expectMemberAdded(groupId: number, contactId: number) { + expect(chat.added.some(a => a.groupId === groupId && a.contactId === contactId)).toBe(true) +} + +function expectCardDeleted(cardItemId: number) { + expect(chat.deleted.some(d => d.itemIds.includes(cardItemId))).toBe(true) +} + +// ─── Event factories ─── + +function connectedEvent(groupId: number, member: any, memberContact?: any) { + return { + type: "connectedToGroupMember" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {}), + member, + ...(memberContact !== undefined ? {memberContact} : {}), + } +} + +function leftEvent(groupId: number, member: any) { + return { + type: "leftMember" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {}), + member: {...member, memberStatus: GroupMemberStatus.Left}, + } +} + +function updatedEvent(groupId: number, chatItem: any, userId = MAIN_USER_ID) { + return { + type: "chatItemUpdated" as const, + user: makeUser(userId), + chatItem: { + chatInfo: {type: "group", groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {})}, + chatItem, + }, + } +} + +function reactionEvent(groupId: number, added: boolean) { + return { + type: "chatItemReaction" as const, + user: makeUser(MAIN_USER_ID), + added, + reaction: { + chatInfo: {type: "group", groupInfo: makeGroupInfo(groupId)}, + chatReaction: {reaction: {type: "emoji", emoji: "👍"}}, + }, + } +} + +function joinedEvent(groupId: number, member: any, userId = MAIN_USER_ID) { + return { + type: "joinedGroupMember" as const, + user: makeUser(userId), + groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {}), + member, + } +} + +function grokViewCustomerMessage(text: string, msgType?: string) { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + const ci = makeChatItem({dir: "groupRcv", text, memberId: CUSTOMER_ID, ...(msgType ? {msgType} : {})}) + return { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci}], + } +} + +// ═══════════════════════════════════════════════════════════ +// Tests +// ═══════════════════════════════════════════════════════════ + +describe("Welcome & First Message", () => { + beforeEach(() => setup()) + + test("first message → queue reply sent, card created in team group", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBeGreaterThan(0) + expect(teamMsgs[teamMsgs.length - 1]).toContain(`/'join ${CUSTOMER_GROUP_ID}'`) + }) + + test("non-text first message → no queue reply, no card", async () => { + await bot.onNewChatItems(customerNonTextMessage()) + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("second message → no duplicate queue reply", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + addBotMessage("The team will reply to your message") + const countBefore = chat.sentTo(CUSTOMER_GROUP_ID).filter(m => m.includes("The team will reply to your message")).length + await bot.onNewChatItems(customerMessage("Second message")) + const countAfter = chat.sentTo(CUSTOMER_GROUP_ID).filter(m => m.includes("The team will reply to your message")).length + expect(countAfter).toBe(countBefore) + }) + + test("unrecognized /command → treated as normal message", async () => { + await bot.onNewChatItems(customerMessage("/unknown")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) +}) + +describe("/grok Activation", () => { + beforeEach(() => setup()) + + test("/grok from QUEUE → Grok invited, grokActivatedMessage sent", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expectMemberAdded(CUSTOMER_GROUP_ID, GROK_CONTACT_ID) + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + }) + + test("/grok as first message → WELCOME→GROK directly, no queue message", async () => { + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("/grok in TEAM → rejected with teamLockedMessage", async () => { + await reachTeam() + await bot.onNewChatItems(customerMessage("/grok")) + expectSentToGroup(CUSTOMER_GROUP_ID, "team mode") + }) + + test("/grok when grokContactId is null → grokUnavailableMessage", async () => { + setup({grokContactId: null}) + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + }) + + test("/grok as first message + Grok join fails → queue message sent as fallback", async () => { + chat.apiAddMemberWillFail() + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) +}) + +describe("Grok Conversation", () => { + beforeEach(() => setup()) + + test("Grok per-message: reads history, calls API, sends response", async () => { + addCustomerMessageToHistory("How do I create a group?", GROK_LOCAL_GROUP_ID) + grokApi.willRespond("To create a group, tap +, then New Group.") + await bot.onGrokNewChatItems(grokViewCustomerMessage("How do I create a group?")) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("How do I create a group?") + expectAnySent("To create a group, tap +, then New Group.") + }) + + test("customer non-text in GROK → no Grok API call", async () => { + await bot.onGrokNewChatItems(grokViewCustomerMessage("", "image")) + expect(grokApi.calls.length).toBe(0) + }) + + test("Grok API error → error message in group, stays GROK", async () => { + grokApi.willFail() + await bot.onGrokNewChatItems(grokViewCustomerMessage("A question")) + expectAnySent("couldn't process that") + }) + + test("Grok ignores bot commands from customer", async () => { + await bot.onGrokNewChatItems(grokViewCustomerMessage("/team")) + expect(grokApi.calls.length).toBe(0) + }) + + test("Grok per-message: history includes prior Grok sent response as assistant", async () => { + addCustomerMessageToHistory("How do I create a group?", GROK_LOCAL_GROUP_ID) + addBotMessage("To create a group, tap + then New Group.", GROK_LOCAL_GROUP_ID) + addCustomerMessageToHistory("How do I invite members?", GROK_LOCAL_GROUP_ID) + grokApi.willRespond("Open the group and tap Invite.") + await bot.onGrokNewChatItems(grokViewCustomerMessage("How do I invite members?")) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("How do I invite members?") + expect(grokApi.calls[0].history).toEqual([ + {role: "user", content: "How do I create a group?"}, + {role: "assistant", content: "To create a group, tap + then New Group."}, + ]) + }) + + test("Grok ignores non-customer messages", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + const ci = makeChatItem({dir: "groupRcv", text: "Team message", memberId: "not-customer", memberContactId: TEAM_MEMBER_1_ID}) + const grokEvt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci}], + } + await bot.onGrokNewChatItems(grokEvt) + expect(grokApi.calls.length).toBe(0) + }) + + test("Grok ignores own messages (groupSnd)", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + const ci = makeChatItem({dir: "groupSnd", text: "My own response"}) + const grokEvt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci}], + } + await bot.onGrokNewChatItems(grokEvt) + expect(grokApi.calls.length).toBe(0) + }) + + test("batch: multiple customer messages in one event → only last triggers Grok API call", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + addCustomerMessageToHistory("First question", GROK_LOCAL_GROUP_ID) + addCustomerMessageToHistory("Second question", GROK_LOCAL_GROUP_ID) + + const ci1 = makeChatItem({dir: "groupRcv", text: "First question", memberId: CUSTOMER_ID}) + const ci2 = makeChatItem({dir: "groupRcv", text: "Second question", memberId: CUSTOMER_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci1}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci2}, + ], + } + + await bot.onGrokNewChatItems(evt) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("Second question") + }) + + test("batch: messages from different groups → each group gets one response", async () => { + const GROK_GROUP_A = 201 + const GROK_GROUP_B = 202 + chat.groups.set(GROK_GROUP_A, makeGroupInfo(GROK_GROUP_A)) + chat.groups.set(GROK_GROUP_B, makeGroupInfo(GROK_GROUP_B)) + addCustomerMessageToHistory("Question A", GROK_GROUP_A) + addCustomerMessageToHistory("Question B", GROK_GROUP_B) + + const ciA = makeChatItem({dir: "groupRcv", text: "Question A", memberId: CUSTOMER_ID}) + const ciB = makeChatItem({dir: "groupRcv", text: "Question B", memberId: CUSTOMER_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_A)}, chatItem: ciA}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_B)}, chatItem: ciB}, + ], + } + + await bot.onGrokNewChatItems(evt) + + expect(grokApi.calls.length).toBe(2) + }) + + test("batch: non-customer messages filtered, only customer messages trigger response", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + addCustomerMessageToHistory("Customer question", GROK_LOCAL_GROUP_ID) + + const custCi = makeChatItem({dir: "groupRcv", text: "Customer question", memberId: CUSTOMER_ID}) + const teamCi = makeChatItem({dir: "groupRcv", text: "Team reply", memberId: "not-customer", memberContactId: TEAM_MEMBER_1_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: custCi}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: teamCi}, + ], + } + + await bot.onGrokNewChatItems(evt) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("Customer question") + }) + + test("batch: across groups → Grok calls overlap in-flight (parallel dispatch)", async () => { + const GROK_GROUP_A = 201 + const GROK_GROUP_B = 202 + chat.groups.set(GROK_GROUP_A, makeGroupInfo(GROK_GROUP_A)) + chat.groups.set(GROK_GROUP_B, makeGroupInfo(GROK_GROUP_B)) + addCustomerMessageToHistory("A", GROK_GROUP_A) + addCustomerMessageToHistory("B", GROK_GROUP_B) + + const ciA = makeChatItem({dir: "groupRcv", text: "A", memberId: CUSTOMER_ID}) + const ciB = makeChatItem({dir: "groupRcv", text: "B", memberId: CUSTOMER_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_A)}, chatItem: ciA}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_B)}, chatItem: ciB}, + ], + } + + // Block both chat() calls until we release. If the handler serialized + // per-group work, only one call would enter chat() before release. + grokApi.blockChat() + const done = bot.onGrokNewChatItems(evt) + // Let both tasks run up to the gate. + await new Promise(r => setTimeout(r, 10)) + expect(grokApi.calls.length).toBe(2) + grokApi.releaseChat() + await done + }) +}) + +describe("/team Activation", () => { + beforeEach(() => setup()) + + test("/team from QUEUE → ALL team members added, teamAddedMessage sent", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/team")) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_2_ID) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + }) + + test("/team as first message → WELCOME→TEAM, no queue message", async () => { + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) + + test("/team when already activated → teamAlreadyInvitedMessage", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "already been invited") + }) + + test("/team with no team members → noTeamMembersMessage", async () => { + setup({teamMembers: []}) + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "No team members are available") + }) +}) + +describe("One-Way Gate", () => { + beforeEach(() => setup()) + + test("team member sends first TEXT → Grok removed if present", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember(), makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?")) + expect(chat.removed.some(r => r.groupId === CUSTOMER_GROUP_ID && r.memberIds.includes(7777))).toBe(true) + }) + + test("team member non-text (no ciContentText) → Grok NOT removed", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember()]) + await bot.onNewChatItems(teamMemberMessage("", TEAM_MEMBER_1_ID)) + expect(chat.removed.length).toBe(0) + }) + + test("/grok after gate → teamLockedMessage", async () => { + await reachTeam() + await bot.onNewChatItems(customerMessage("/grok")) + expectSentToGroup(CUSTOMER_GROUP_ID, "team mode") + }) + + test("customer text in TEAM → card update scheduled, no bot reply", async () => { + await reachTeam() + const sentBefore = chat.sentTo(CUSTOMER_GROUP_ID).length + await bot.onNewChatItems(customerMessage("Follow-up question")) + const sentAfter = chat.sentTo(CUSTOMER_GROUP_ID).length + expect(sentAfter).toBe(sentBefore) + }) + + test("/grok in TEAM-PENDING → invite Grok if not present", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expectMemberAdded(CUSTOMER_GROUP_ID, GROK_CONTACT_ID) + }) +}) + +describe("One-Way Gate with Grok Disabled", () => { + test("team text removes Grok even when grokApi is null", async () => { + setup() + // Recreate bot without grokApi but with grokContactId still set (simulates disabled Grok with persisted contact) + bot = new SupportBot(chat as any, null, config as any, MAIN_USER_ID, null, DESIRED_COMMANDS) + bot.cards = cards + // Reach QUEUE state with Grok + team member already present + addBotMessage("The team will reply to your message") + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember(), makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member sends text → one-way gate should fire + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?")) + expect(chat.removed.some(r => r.groupId === CUSTOMER_GROUP_ID && r.memberIds.includes(7777))).toBe(true) + }) + + test("Grok does not respond when disabled even if grokContactId is set", async () => { + setup() + bot = new SupportBot(chat as any, null, config as any, MAIN_USER_ID, null, DESIRED_COMMANDS) + bot.cards = cards + // Set up group with Grok member present + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember()]) + addBotMessage("The team will reply to your message") + // Customer sends text in GROK state + await bot.onNewChatItems(customerMessage("How do I use SimpleX?")) + // Grok should not respond (grokApi is null) + expect(grokApi.calls.length).toBe(0) + }) +}) + +describe("Team Member Lifecycle", () => { + beforeEach(() => setup()) + + test("team member connected → promoted to Owner", async () => { + await bot.onMemberConnected(connectedEvent(CUSTOMER_GROUP_ID, makeTeamMember(TEAM_MEMBER_1_ID, "Alice"))) + expect(chat.roleChanges.some(r => r.groupId === CUSTOMER_GROUP_ID && r.memberIds.includes(5000 + TEAM_MEMBER_1_ID) && r.role === GroupMemberRole.Owner)).toBe(true) + }) + + test("/team invites team member → apiSetMembersRole(Owner) called at invite time", async () => { + await bot.onNewChatItems(customerMessage("/team")) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + expect(chat.roleChanges.some(r => + r.groupId === CUSTOMER_GROUP_ID + && r.memberIds.includes(5000 + TEAM_MEMBER_1_ID) + && r.role === GroupMemberRole.Owner + )).toBe(true) + }) + + test("/join invites team member → apiSetMembersRole(Owner) called at invite time", async () => { + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}`)) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + expect(chat.roleChanges.some(r => + r.groupId === CUSTOMER_GROUP_ID + && r.memberIds.includes(5000 + TEAM_MEMBER_1_ID) + && r.role === GroupMemberRole.Owner + )).toBe(true) + }) + + test("/team when team member already in group (any non-terminal status) → apiSetMembersRole NOT re-called", async () => { + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await cards.mergeCustomData(CUSTOMER_GROUP_ID, {state: "TEAM-PENDING"}) + chat.added.length = 0 + chat.roleChanges.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.added.length).toBe(0) + expect(chat.roleChanges.length).toBe(0) + }) + + test("/join when team member already in group → apiSetMembersRole NOT re-called", async () => { + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + chat.added.length = 0 + chat.roleChanges.length = 0 + + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}`)) + expect(chat.added.length).toBe(0) + expect(chat.roleChanges.length).toBe(0) + }) + + test("customer connected → NOT promoted to Owner", async () => { + await bot.onMemberConnected(connectedEvent(CUSTOMER_GROUP_ID, makeCustomerMember())) + expect(chat.roleChanges.length).toBe(0) + }) + + test("Grok connected → NOT promoted to Owner", async () => { + await bot.onMemberConnected(connectedEvent(CUSTOMER_GROUP_ID, makeGrokMember())) + expect(chat.roleChanges.length).toBe(0) + }) + + test("all team members leave before sending → state stays TEAM-PENDING", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + // Remove team members from the group + chat.members.set(CUSTOMER_GROUP_ID, []) + // State is authoritative and monotonic — composition changes never demote it. + // Customer is still waiting for the team's response. + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("TEAM-PENDING") + }) + + test("/team after all team members left (TEAM-PENDING, no msg sent) → re-adds members", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, []) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) + + test("/team after all team members left (TEAM, msg was sent) → re-adds members", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + addTeamMemberMessageToHistory("Hi, how can I help?", TEAM_MEMBER_1_ID) + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?")) + + // All team members leave + chat.members.set(CUSTOMER_GROUP_ID, []) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) +}) + +describe("Card Dashboard", () => { + beforeEach(() => setup()) + + test("first message creates card with /'join ' final line", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(1) + const card = teamMsgs[0] + expect(card).toContain(`/'join ${CUSTOMER_GROUP_ID}'`) + // Join command is the final line of the card + const lines = card.split("\n") + expect(lines[lines.length - 1]).toBe(`/'join ${CUSTOMER_GROUP_ID}'`) + }) + + test("card update deletes old card then posts new one", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 555}) + await cards.flush() + expect(chat.deleted.length).toBe(0) + + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + await cards.flush() + expectCardDeleted(555) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("apiDeleteChatItems failure → ignored, new card posted", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 555}) + chat.apiDeleteChatItemsWillFail() + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + await cards.flush() + // New card should still be posted despite delete failure + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("customData stores cardItemId → survives flush cycle", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + // After card creation, customData should have cardItemId + const data = chat.customData.get(CUSTOMER_GROUP_ID) + expect(data).toBeDefined() + expect(typeof data.cardItemId).toBe("number") + }) + + test("concurrent mergeCustomData on same group → both patches survive", async () => { + // Without per-group serialization, two overlapping mergeCustomData calls + // can both read the same snapshot and the second write clobbers the first + // patch. The mutex keeps them ordered so both patches land. + const GID = 500 + chat.groups.set(GID, makeGroupInfo(GID)) + + const pA = cards.mergeCustomData(GID, {state: "QUEUE"}) + const pB = cards.mergeCustomData(GID, {cardItemId: 123}) + await Promise.all([pA, pB]) + + const final = chat.customData.get(GID) + expect(final.state).toBe("QUEUE") + expect(final.cardItemId).toBe(123) + }) + + test("customer leaves → customData cleared", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 999}) + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeCustomerMember())) + expect(chat.customData.has(CUSTOMER_GROUP_ID)).toBe(false) + }) +}) + +describe("Card Debouncing", () => { + beforeEach(() => setup()) + + test("rapid events within flush interval → single card update on flush", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 500}) + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + await cards.flush() + // Only one delete and one post + expect(chat.deleted.length).toBe(1) + // Multiple schedules → single update (one card message) + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(1) + }) + + test("multiple groups pending → each reposted once per flush", async () => { + const GROUP_A = 101 + const GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + chat.customData.set(GROUP_B, {cardItemId: 502}) + cards.scheduleUpdate(GROUP_A) + cards.scheduleUpdate(GROUP_B) + await cards.flush() + expect(chat.deleted.length).toBe(2) + }) + + test("card create is immediate (not debounced)", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + // Card should be posted immediately without flush + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("flush with no pending updates → no-op", async () => { + await cards.flush() + expect(chat.deleted.length).toBe(0) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("flush on group with no cardItemId → createCard posts a new card (retries failed create)", async () => { + // customData without cardItemId simulates a prior createCard that failed + // mid-flight and re-queued itself. flushOne must dispatch to createCard, + // not updateCard (which would early-return). + const GID = 777 + chat.groups.set(GID, makeGroupInfo(GID)) + chat.customData.set(GID, {state: "QUEUE"}) + cards.scheduleUpdate(GID) + await cards.flush() + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(1) + expect(typeof chat.customData.get(GID).cardItemId).toBe("number") + }) +}) + +describe("Card Format & State Derivation", () => { + beforeEach(() => setup()) + + test("QUEUE state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "QUEUE"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("QUEUE") + }) + + test("WELCOME state when customData.state is absent", async () => { + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("WELCOME") + }) + + test("GROK state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "GROK"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("GROK") + }) + + test("TEAM-PENDING state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "TEAM-PENDING"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("TEAM-PENDING") + }) + + test("TEAM state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "TEAM"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("TEAM") + }) + + test("message count excludes bot's own messages", async () => { + addCustomerMessageToHistory("Hello") + addBotMessage("Queue message") + addCustomerMessageToHistory("Follow-up") + const chatResult = await cards.getChat(CUSTOMER_GROUP_ID, 100) + const nonBotCount = chatResult.chatItems.filter((ci: any) => ci.chatDir.type !== "groupSnd").length + expect(nonBotCount).toBe(2) + }) +}) + +describe("/join Command (Team Group)", () => { + beforeEach(() => setup()) + + test("/join (numeric only) → team member added to customer group", async () => { + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}`)) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) + + test("/join : (legacy form) → team member added", async () => { + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}:Customer`)) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) + + test("/join validates target is business group → error if not", async () => { + const nonBizGroupId = 999 + chat.groups.set(nonBizGroupId, makeGroupInfo(nonBizGroupId, {businessChat: null})) + await bot.onNewChatItems(teamGroupMessage(`/join ${nonBizGroupId}:Test`)) + expectSentToGroup(TEAM_GROUP_ID, "not a business chat") + }) + + test("/join with non-existent groupId → error in team group", async () => { + await bot.onNewChatItems(teamGroupMessage("/join 99999:Nobody")) + expect(chat.sentTo(TEAM_GROUP_ID).some(m => m.toLowerCase().includes("error"))).toBe(true) + }) + + test("customer sending /join in customer group → treated as normal message", async () => { + await bot.onNewChatItems(customerMessage("/join 50:Test")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) + + test("/join with non-numeric params → error reply, no apiAddMember call", async () => { + await bot.onNewChatItems(teamGroupMessage("/join abc")) + expectSentToGroup(TEAM_GROUP_ID, `Error: invalid group id "abc"`) + expect(chat.added.length).toBe(0) + }) +}) + +describe("DM Handshake", () => { + beforeEach(() => setup()) + + test("team member joins team group → DM sent with contact ID", async () => { + const member = {memberId: "new-team", groupMemberId: 8000, memberContactId: 30, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Charlie"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, member, {contactId: 30, profile: {displayName: "Charlie"}})) + expectDmSent(30, "Your contact ID is 30:Charlie") + }) + + test("DM with spaces in name → name single-quoted", async () => { + const member = {memberId: "new-team", groupMemberId: 8001, memberContactId: 31, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Charlie Brown"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, member, {contactId: 31, profile: {displayName: "Charlie Brown"}})) + expectDmSent(31, "31:'Charlie Brown'") + }) + + test("pending DM delivered on contactConnected", async () => { + const invEvt = { + type: "newMemberContactReceivedInv" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 32, profile: {displayName: "Dave"}}, + groupInfo: makeGroupInfo(TEAM_GROUP_ID, {businessChat: null}), + member: {memberId: "dave-member", groupMemberId: 8002, memberContactId: 32, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Dave"}}, + } + await bot.onMemberContactReceivedInv(invEvt) + + await bot.onContactConnected({ + type: "contactConnected" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 32, profile: {displayName: "Dave"}}, + }) + expectDmSent(32, "Your contact ID is 32:Dave") + }) + + test("team member with no DM contact → creates member contact and sends invitation", async () => { + const member = {memberId: "new-team-no-dm", groupMemberId: 8010, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Frank"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, member, undefined)) + expect(chat.memberContacts.some(c => c.groupId === TEAM_GROUP_ID && c.groupMemberId === 8010)).toBe(true) + expect(chat.memberContactInvitations.some(i => i.text.includes("Your contact ID is") && i.text.includes("Frank"))).toBe(true) + const dms = chat.sent.filter(s => s.chat[0] === ChatType.Direct) + expect(dms.some(m => m.text.includes("Your contact ID is") && m.text.includes("Frank"))).toBe(true) + }) + + test("joinedGroupMember in team group → creates member contact and sends invitation", async () => { + const member = {memberId: "link-joiner", groupMemberId: 8020, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Grace"}} + await bot.onJoinedGroupMember(joinedEvent(TEAM_GROUP_ID, member)) + expect(chat.memberContacts.some(c => c.groupId === TEAM_GROUP_ID && c.groupMemberId === 8020)).toBe(true) + expect(chat.memberContactInvitations.some(i => i.text.includes("Grace"))).toBe(true) + }) + + test("no duplicate DM when both sendTeamMemberDM succeeds and onMemberContactReceivedInv fires", async () => { + const invEvt = { + type: "newMemberContactReceivedInv" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 33, profile: {displayName: "Eve"}}, + groupInfo: makeGroupInfo(TEAM_GROUP_ID, {businessChat: null}), + member: {memberId: "eve-member", groupMemberId: 8003, memberContactId: 33, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Eve"}}, + } + await bot.onMemberContactReceivedInv(invEvt) + + const eveMember = {memberId: "eve-member", groupMemberId: 8003, memberContactId: 33, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Eve"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, eveMember, {contactId: 33, profile: {displayName: "Eve"}})) + + await bot.onContactConnected({ + type: "contactConnected" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 33, profile: {displayName: "Eve"}}, + }) + + const dms = chat.sentDirect(33) + const contactIdMsgs = dms.filter(m => m.includes("Your contact ID is 33:Eve")) + expect(contactIdMsgs.length).toBe(1) + }) +}) + +describe("Direct Message Handling", () => { + beforeEach(() => setup()) + + test("regular DM → bot replies with business address link", async () => { + bot.businessAddress = "simplex:/contact#abc123" + await bot.onNewChatItems(directMessage("Hi there", 99)) + expectDmSent(99, "simplex:/contact#abc123") + }) + + test("DM without business address set → no reply", async () => { + bot.businessAddress = null + await bot.onNewChatItems(directMessage("Hi there", 99)) + expect(chat.sentDirect(99).length).toBe(0) + }) + + test("non-message DM event (e.g. contactConnected) → no reply", async () => { + bot.businessAddress = "simplex:/contact#abc123" + const ci = { + chatDir: {type: "directRcv"}, + content: {type: "rcvDirectEvent"}, + meta: {itemId: 9999, createdAt: new Date().toISOString()}, + } + const evt = { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeDirectAChatItem(ci, 99)], + } + await bot.onNewChatItems(evt) + expect(chat.sentDirect(99).length).toBe(0) + }) +}) + +describe("Business Request Handler", () => { + beforeEach(() => setup()) + + test("acceptingBusinessRequest → enables file uploads AND visible history", async () => { + await bot.onBusinessRequest({ + type: "acceptingBusinessRequest" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(CUSTOMER_GROUP_ID), + }) + expect(chat.profileUpdates.some(u => + u.groupId === CUSTOMER_GROUP_ID + && u.profile.groupPreferences?.files?.enable === GroupFeatureEnabled.On + && u.profile.groupPreferences?.history?.enable === GroupFeatureEnabled.On + )).toBe(true) + }) +}) + +describe("chatItemUpdated Handler", () => { + beforeEach(() => setup()) + + test("chatItemUpdated in business group → card update scheduled", async () => { + await bot.onChatItemUpdated(updatedEvent(CUSTOMER_GROUP_ID, makeChatItem({dir: "groupRcv", text: "edited message", memberId: CUSTOMER_ID}))) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 600}) + await cards.flush() + expectCardDeleted(600) + }) + + test("chatItemUpdated in non-business group → ignored", async () => { + await bot.onChatItemUpdated(updatedEvent(TEAM_GROUP_ID, makeChatItem({dir: "groupRcv", text: "team msg"}))) + await cards.flush() + expect(chat.deleted.length).toBe(0) + }) + + test("chatItemUpdated from wrong user → ignored", async () => { + await bot.onChatItemUpdated(updatedEvent(CUSTOMER_GROUP_ID, makeChatItem({dir: "groupRcv", text: "edited"}), GROK_USER_ID)) + await cards.flush() + expect(chat.deleted.length).toBe(0) + }) +}) + +describe("Reactions", () => { + beforeEach(() => setup()) + + test("reaction in business group → card update scheduled", async () => { + await bot.onChatItemReaction(reactionEvent(CUSTOMER_GROUP_ID, true)) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 700}) + await cards.flush() + expectCardDeleted(700) + }) + + test("reaction removed (added=false) → no card update", async () => { + await bot.onChatItemReaction(reactionEvent(CUSTOMER_GROUP_ID, false)) + await cards.flush() + expect(chat.deleted.length).toBe(0) + }) +}) + +describe("Customer Leave", () => { + beforeEach(() => setup()) + + test("customer leaves → customData cleared", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 800}) + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeCustomerMember())) + expect(chat.customData.has(CUSTOMER_GROUP_ID)).toBe(false) + }) + + test("Grok leaves → in-memory maps cleaned", async () => { + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeGrokMember())) + }) + + test("team member leaves → logged, no crash", async () => { + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeTeamMember(TEAM_MEMBER_1_ID, "Alice"))) + }) + + test("leftMember in non-business group → ignored", async () => { + const member = {memberId: "someone", groupMemberId: 9000, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Someone"}} + await bot.onLeftMember(leftEvent(TEAM_GROUP_ID, member)) + }) +}) + +describe("Error Handling", () => { + beforeEach(() => setup()) + + test("apiAddMember fails (Grok invite) → grokUnavailableMessage", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + chat.apiAddMemberWillFail() + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + }) + + test("groupDuplicateMember on Grok invite → only inviting message, no result", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + chat.apiAddMemberWillFail({chatError: {errorType: {type: "groupDuplicateMember"}}}) + const sentBefore = chat.sent.length + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + // Only the "Inviting Grok" message is sent — no activated/unavailable result + expect(chat.sent.length).toBe(sentBefore + 1) + expectSentToGroup(CUSTOMER_GROUP_ID, "Inviting Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + }) + + test("/team while members are in Invited status → no second apiAddMember call", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + + // Simulate the realistic post-/team state: both members have been invited + // but have not yet accepted (memberStatus = "invited"). The SimpleX API + // would resend the invitation if apiAddMember is called for an Invited + // member — the pre-check in addOrFindTeamMember must skip them. + const invited = (contactId: number) => ({ + memberId: `team-${contactId}`, + groupMemberId: 5000 + contactId, + memberContactId: contactId, + memberStatus: "invited", + memberProfile: {displayName: `Contact${contactId}`}, + }) + chat.members.set(CUSTOMER_GROUP_ID, [invited(TEAM_MEMBER_1_ID), invited(TEAM_MEMBER_2_ID)]) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.added.filter(a => a.groupId === CUSTOMER_GROUP_ID)).toEqual([]) + }) + + test("/grok in TEAM-PENDING while Grok is in Invited status → no second apiAddMember call", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [ + makeTeamMember(TEAM_MEMBER_1_ID, "Alice"), + {memberId: "grok-member", groupMemberId: 7777, memberContactId: GROK_CONTACT_ID, memberStatus: "invited", memberProfile: {displayName: "Grok"}}, + ]) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + + expect(chat.added.filter(a => a.contactId === GROK_CONTACT_ID)).toEqual([]) + expectNotSentToGroup(CUSTOMER_GROUP_ID, "Inviting Grok") + }) +}) + +describe("Profile / Event Filtering", () => { + beforeEach(() => setup()) + + test("newChatItems from Grok profile → ignored by main handler", async () => { + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [makeAChatItem(makeChatItem({dir: "groupRcv", text: "test"}))], + } + const sentBefore = chat.sent.length + await bot.onNewChatItems(evt) + expect(chat.sent.length).toBe(sentBefore) + }) + + test("Grok events from main profile → ignored by Grok handlers", async () => { + const evt = { + type: "receivedGroupInvitation" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(300), + contact: {contactId: 1}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + } + await bot.onGrokGroupInvitation(evt) + expect(chat.joined.length).toBe(0) + }) + + test("own messages (groupSnd) → ignored", async () => { + const ci = makeChatItem({dir: "groupSnd", text: "Bot message"}) + const evt = { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci)], + } + const sentBefore = chat.sent.length + await bot.onNewChatItems(evt) + expect(chat.sent.length).toBe(sentBefore) + }) + + test("non-business group messages → ignored", async () => { + const ci = makeChatItem({dir: "groupRcv", text: "test"}) + const nonBizGroup = makeGroupInfo(999, {businessChat: null}) + const evt = { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: nonBizGroup}, chatItem: ci}], + } + const sentBefore = chat.sent.length + await bot.onNewChatItems(evt) + expect(chat.sent.length).toBe(sentBefore) + }) +}) + +describe("Grok Join Flow", () => { + beforeEach(() => setup()) + + test("Grok receivedGroupInvitation → apiJoinGroup called", async () => { + // First need to set up a pending grok join + // Simulate the main profile side: add Grok to a group + await reachQueue() + addBotMessage("The team will reply to your message") + + // This kicks off activateGrok which adds member and waits + const joinComplete = new Promise(async (resolve) => { + // Simulate Grok invitation after a small delay + setTimeout(async () => { + const addedGrok = chat.added.find(a => a.contactId === GROK_CONTACT_ID) + if (addedGrok) { + const memberId = `member-${GROK_CONTACT_ID}` + await bot.onGrokGroupInvitation({ + type: "receivedGroupInvitation", + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + }) + } + resolve() + }, 10) + }) + + // Don't await bot.onNewChatItems yet — let it start + const botPromise = bot.onNewChatItems(customerMessage("/grok")) + await joinComplete + // Complete the join + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999}, + }) + await botPromise + + expect(chat.joined).toContain(GROK_LOCAL_GROUP_ID) + }) + + test("unmatched Grok invitation → buffered, not joined", async () => { + const evt = { + type: "receivedGroupInvitation" as const, + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(999), membership: {memberId: "unknown-member"}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + } + await bot.onGrokGroupInvitation(evt) + expect(chat.joined.length).toBe(0) + }) + + test("buffered invitation drained after pendingGrokJoins set → apiJoinGroup called", async () => { + // Simulate the race: invitation arrives before pendingGrokJoins is set + const memberId = `member-${GROK_CONTACT_ID}` + const invEvt = { + type: "receivedGroupInvitation" as const, + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + } + // Buffer the invitation (no pending join registered yet) + await bot.onGrokGroupInvitation(invEvt) + expect(chat.joined.length).toBe(0) + + // Now trigger activateGrok — apiAddMember returns, pendingGrokJoins set, buffer drained + const joinComplete = new Promise((resolve) => { + setTimeout(async () => { + // Grok connected after buffer drain processed the invitation + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999}, + }) + resolve() + }, 20) + }) + + await reachQueue() + addBotMessage("The team will reply to your message") + const botPromise = bot.onNewChatItems(customerMessage("/grok")) + await joinComplete + await botPromise + await bot.flush() + + expect(chat.joined).toContain(GROK_LOCAL_GROUP_ID) + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + }) + + test("per-message responses suppressed during activateGrok initial response", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + + // Customer's message visible in Grok's view (activateGrok reads it for initial response) + addCustomerMessageToHistory("Hello, I need help", GROK_LOCAL_GROUP_ID) + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + + // Start /grok activation (fireAndForget) + const botPromise = bot.onNewChatItems(customerMessage("/grok")) + + // Wait for apiAddMember to complete + await new Promise(r => setTimeout(r, 10)) + + // Simulate Grok invitation → sets grokGroupMap/reverseGrokMap + const memberId = `member-${GROK_CONTACT_ID}` + await bot.onGrokGroupInvitation({ + type: "receivedGroupInvitation", + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + }) + + // grokInitialResponsePending is set, reverseGrokMap is set. + // Simulate per-message event (as if message backlog arrived for Grok profile) + await bot.onGrokNewChatItems(grokViewCustomerMessage("Hello, I need help")) + + // Gating: per-message handler must NOT have called Grok API + expect(grokApi.calls.length).toBe(0) + + // Now complete the join → activateGrok sends initial combined response + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999, memberContactId: undefined}, + }) + + await botPromise + await bot.flush() + + // Only 1 Grok API call: the initial combined response from activateGrok + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toContain("Hello, I need help") + }) + + test("per-message responses resume after activateGrok completes", async () => { + await reachGrok() + await bot.flush() + const callsAfterActivation = grokApi.calls.length + + // Send a new customer message via Grok's view — should be processed normally + addCustomerMessageToHistory("Follow-up question", GROK_LOCAL_GROUP_ID) + await bot.onGrokNewChatItems(grokViewCustomerMessage("Follow-up question")) + + expect(grokApi.calls.length).toBe(callsAfterActivation + 1) + expect(grokApi.calls[grokApi.calls.length - 1].message).toBe("Follow-up question") + }) + + test("activateGrok groupDuplicateMember path → gate cleared by outer finally", async () => { + // After reachGrok(), gate is cleared and reverseGrokMap is populated. + await reachGrok() + await bot.flush() + const callsBaseline = grokApi.calls.length + + // Second /grok while Grok is already present → apiAddMember throws duplicate. + // The outer try/finally must clear the gate even though the handler returns + // silently from inside the try — otherwise per-message responses stay + // suppressed for this group forever. + chat.apiAddMemberWillFail({chatError: {errorType: {type: "groupDuplicateMember"}}}) + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + + // Gate must be clear: a subsequent per-message event triggers Grok. + addCustomerMessageToHistory("another question", GROK_LOCAL_GROUP_ID) + await bot.onGrokNewChatItems(grokViewCustomerMessage("another question")) + expect(grokApi.calls.length).toBe(callsBaseline + 1) + }) +}) + +describe("Grok No-History Fallback", () => { + beforeEach(() => setup()) + + test("Grok joins but sees no customer messages → sends grokNoHistoryMessage", async () => { + chat.chatItems.set(GROK_LOCAL_GROUP_ID, []) + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + + const grokJoinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await grokJoinPromise + await bot.flush() + expectAnySent("couldn't see your earlier messages") + }) +}) + +describe("Non-customer messages trigger card update", () => { + beforeEach(() => setup()) + + test("Grok response in customer group → card update scheduled", async () => { + await bot.onNewChatItems(grokResponseMessage("Grok says hi")) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 900}) + await cards.flush() + expectCardDeleted(900) + }) + + test("team member message → card update scheduled", async () => { + await bot.onNewChatItems(teamMemberMessage("Team says hi")) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 901}) + await cards.flush() + expectCardDeleted(901) + }) +}) + +describe("End-to-End Flows", () => { + beforeEach(() => setup()) + + test("WELCOME → QUEUE → /team → TEAM-PENDING → team msg → TEAM", async () => { + await bot.onNewChatItems(customerMessage("Help me")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + addBotMessage("The team will reply to your message") + + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + addBotMessage("We will reply within 24 hours.") + + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + const pendingState = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(pendingState).toBe("TEAM-PENDING") + + addTeamMemberMessageToHistory("I'll help you", TEAM_MEMBER_1_ID) + await bot.onNewChatItems(teamMemberMessage("I'll help you")) + + const teamState = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(teamState).toBe("TEAM") + }) + + test("WELCOME → /grok first msg → GROK", async () => { + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("multiple concurrent conversations are independent", async () => { + const GROUP_A = 101 + const GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A, {customerId: "cust-a"})) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B, {customerId: "cust-b"})) + + const ciA = makeChatItem({dir: "groupRcv", text: "Help A", memberId: "cust-a"}) + await bot.onNewChatItems({ + type: "newChatItems", + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROUP_A, {customerId: "cust-a"})}, chatItem: ciA}], + }) + + const ciB = makeChatItem({dir: "groupRcv", text: "Help B", memberId: "cust-b"}) + await bot.onNewChatItems({ + type: "newChatItems", + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROUP_B, {customerId: "cust-b"})}, chatItem: ciB}], + }) + + expectSentToGroup(GROUP_A, "The team will reply to your message") + expectSentToGroup(GROUP_B, "The team will reply to your message") + }) +}) + +describe("Message Templates", () => { + test("welcomeMessage is a non-empty string", () => { + expect(typeof welcomeMessage).toBe("string") + expect(welcomeMessage.length).toBeGreaterThan(0) + }) + + test("grokActivatedMessage mentions chatting with Grok", () => { + expect(grokActivatedMessage).toContain("chatting with Grok") + }) + + test("teamLockedMessage mentions team mode", () => { + expect(teamLockedMessage).toContain("team mode") + }) + + test("queueMessage mentions hours", () => { + const msg = queueMessage("UTC", true) + expect(msg).toContain("hours") + }) +}) + +describe("State persistence in customData", () => { + beforeEach(() => setup()) + + test("first customer text writes state=QUEUE to customData", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("QUEUE") + }) + + test("/team writes state=TEAM-PENDING immediately (before team accepts)", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM-PENDING") + }) + + test("/grok writes state=GROK when activation succeeds", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("GROK") + }) + + test("/grok from QUEUE reverts state to QUEUE if activation fails", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + chat.apiAddMemberWillFail() + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("QUEUE") + }) + + test("concurrent /team during Grok activation timeout does not demote state", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + + // Pause activateGrok at waitForGrokJoin so /team can run in the meantime. + // Patching apiAddMember won't work: it's wrapped in withMainProfile's mutex, + // which /team's activateTeam also needs. waitForGrokJoin awaits outside the + // mutex — that's the real race window in production. + let releaseJoin!: (joined: boolean) => void + ;(bot as any).waitForGrokJoin = () => + new Promise((resolve) => { releaseJoin = resolve }) + + // /grok: writes state=GROK optimistically, fire-and-forgets activateGrok. + await bot.onNewChatItems(customerMessage("/grok")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("GROK") + + // Let activateGrok progress past apiAddMember into waitForGrokJoin. + await Promise.resolve() + await Promise.resolve() + + // /team while activateGrok is waiting for join — writes TEAM-PENDING + adds members. + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM-PENDING") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + + // Simulate Grok join timeout — activateGrok's revertStateOnFail runs. + releaseJoin(false) + await bot.flush() + + // Fix asserts: revert guard sees state != "GROK" and leaves TEAM-PENDING alone. + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM-PENDING") + }) + + test("first team text writes state=TEAM via gate", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await bot.onNewChatItems(teamMemberMessage("I'll help")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM") + }) +}) + +describe("Card Preview Sender Prefixes", () => { + beforeEach(() => setup()) + + // Helper: extract preview line from card text posted to team group + function getCardPreview(): string { + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + const cardText = teamMsgs[0] + if (!cardText) return "" + const lines = cardText.split("\n") + // Card layout: header, state, preview, /'join N' — preview is second to last + return lines[lines.length - 2] || "" + } + + test("customer-only messages: first prefixed, rest not", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Hello") + addCustomerMessageToHistory("Need help") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Alice: Hello") + expect(preview).toContain("!3 /! Need help") + // Second message must NOT have prefix (same sender) + expect(preview).not.toContain("Alice: Need help") + }) + + test("three consecutive customer messages: only first gets prefix", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("First") + addCustomerMessageToHistory("Second") + addCustomerMessageToHistory("Third") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + const prefixCount = (preview.match(/Alice:/g) || []).length + expect(prefixCount).toBe(1) + expect(preview).toContain("Alice: First") + }) + + test("alternating customer and Grok: each sender change triggers prefix", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("How does encryption work?") + addGrokMessageToHistory("SimpleX uses double ratchet") + addCustomerMessageToHistory("And metadata?") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Alice: How does encryption work?") + expect(preview).toContain("Grok: SimpleX uses double ratchet") + expect(preview).toContain("Alice: And metadata?") + }) + + test("Grok identified by grokContactId, not by display name", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + // Grok message uses GROK_CONTACT_ID → labeled "Grok" regardless of memberProfile + addGrokMessageToHistory("I am Grok") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Grok: I am Grok") + }) + + test("team member messages use their memberProfile displayName", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Help please") + // Add team member message with explicit display name + const teamCi = makeChatItem({ + dir: "groupRcv", text: "On it!", + memberId: `team-${TEAM_MEMBER_1_ID}`, memberContactId: TEAM_MEMBER_1_ID, + memberDisplayName: "Bob", + }) + const items = chat.chatItems.get(CUSTOMER_GROUP_ID) || [] + items.push(teamCi) + chat.chatItems.set(CUSTOMER_GROUP_ID, items) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Alice: Help please") + expect(preview).toContain("Bob: On it!") + }) + + test("bot messages (groupSnd) excluded from preview", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Hello") + addBotMessage("The team will reply to your message") + addCustomerMessageToHistory("Thanks") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).not.toContain("The team will reply to your message") + // Both customer messages are from the same sender — only first prefixed + expect(preview).toContain("Alice: Hello") + expect(preview).toContain("!3 /! Thanks") + }) + + test("media-only message shows type label", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + const imgCi = makeChatItem({dir: "groupRcv", text: "", memberId: CUSTOMER_ID, msgType: "image"}) + const items = chat.chatItems.get(CUSTOMER_GROUP_ID) || [] + items.push(imgCi) + chat.chatItems.set(CUSTOMER_GROUP_ID, items) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[image]") + }) + + test("media message with caption shows label + text", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + const imgCi = makeChatItem({dir: "groupRcv", text: "screenshot of the bug", memberId: CUSTOMER_ID, msgType: "image"}) + const items = chat.chatItems.get(CUSTOMER_GROUP_ID) || [] + items.push(imgCi) + chat.chatItems.set(CUSTOMER_GROUP_ID, items) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[image] screenshot of the bug") + }) + + test("long message truncated with [truncated]", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + const longMsg = "x".repeat(300) + addCustomerMessageToHistory(longMsg) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[truncated]") + // Truncated at ~200 chars + prefix + expect(preview.length).toBeLessThan(300) + }) + + test("total overflow truncates oldest messages, keeps newest", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + // Add many messages to exceed 1000 chars total + for (let i = 0; i < 20; i++) { + addCustomerMessageToHistory(`Message number ${i} with some extra padding text to fill space quickly`) + } + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[truncated]") + // Newest messages should be present, oldest truncated + expect(preview).toContain("Message number 19") + expect(preview).not.toContain("Message number 0") + // Should not include all 20 messages + const slashCount = (preview.match(/ \/ /g) || []).length + expect(slashCount).toBeLessThan(19) + }) + + test("empty preview when no messages", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toBe('""') + }) + + test("only bot messages → empty preview", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addBotMessage("Welcome!") + addBotMessage("Queue message") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toBe('""') + }) + + test("newlines in message text → replaced with spaces", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("line1\nline2\n\nline3") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).not.toContain("\n") + expect(preview).toContain("line1 line2 line3") + }) + + test("newlines in customer display name → sanitized in card header", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "First\nLast"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Hello") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(1) + const cardText = teamMsgs[0] + // Card header should have sanitized name (no newlines) + expect(cardText).toContain("First Last") + // Exactly 4 lines: header, state, preview, /'join N' + expect(cardText.split("\n").length).toBe(4) + expect(cardText).toContain(`/'join ${CUSTOMER_GROUP_ID}'`) + }) +}) + +describe("Restart Card Recovery", () => { + beforeEach(() => setup()) + + test("refreshAllCards refreshes groups with active cards", async () => { + const GROUP_A = 101 + const GROUP_B = 102 + const GROUP_NO_CARD = 103 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.groups.set(GROUP_NO_CARD, makeGroupInfo(GROUP_NO_CARD)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + chat.customData.set(GROUP_B, {cardItemId: 503}) + + await cards.refreshAllCards() + + expectCardDeleted(501) + expectCardDeleted(503) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(2) // 2 cards × 1 message each + }) + + test("refreshAllCards with no active cards → no-op", async () => { + await cards.refreshAllCards() + expect(chat.deleted.length).toBe(0) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("refreshAllCards ignores groups without cardItemId in customData", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.customData.set(GROUP_A, {someOtherData: true}) + + await cards.refreshAllCards() + expect(chat.deleted.length).toBe(0) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("refreshAllCards orders by cardItemId ascending (oldest first, newest last)", async () => { + // GROUP_C has higher cardItemId (more recent) than GROUP_A and GROUP_B + const GROUP_A = 101, GROUP_B = 102, GROUP_C = 103 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.groups.set(GROUP_C, makeGroupInfo(GROUP_C)) + chat.customData.set(GROUP_C, {cardItemId: 900}) // newest — should refresh last + chat.customData.set(GROUP_A, {cardItemId: 100}) // oldest — should refresh first + chat.customData.set(GROUP_B, {cardItemId: 500}) // middle + + await cards.refreshAllCards() + + // Verify deletion order: oldest cardItemId first, newest last + expect(chat.deleted.length).toBe(3) + expect(chat.deleted[0].itemIds).toEqual([100]) + expect(chat.deleted[1].itemIds).toEqual([500]) + expect(chat.deleted[2].itemIds).toEqual([900]) + + // Newest card is posted last → appears at bottom of team group + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(3) // 3 cards × 1 message each + }) + + test("refreshAllCards skips cards marked complete", async () => { + const GROUP_A = 101, GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.customData.set(GROUP_A, {cardItemId: 100, complete: true}) + chat.customData.set(GROUP_B, {cardItemId: 200}) + + await cards.refreshAllCards() + + expect(chat.deleted.length).toBe(1) + expect(chat.deleted[0].itemIds).toEqual([200]) + expect(chat.deleted.some(d => d.itemIds.includes(100))).toBe(false) + }) + + test("refreshAllCards deletes old card before reposting", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + + await cards.refreshAllCards() + + // Old card should be deleted + expect(chat.deleted.length).toBe(1) + expect(chat.deleted[0].itemIds).toEqual([501]) + // New card posted + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(1) + }) + + test("refreshAllCards ignores delete failure (>24h old card)", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + chat.apiDeleteChatItemsWillFail() + + await cards.refreshAllCards() + + // Delete failed but new card still posted + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(1) + // customData updated with new cardItemId + const newData = chat.customData.get(GROUP_A) + expect(typeof newData.cardItemId).toBe("number") + expect(newData.cardItemId).not.toBe(501) // new ID, not the old one + }) + + test("card flush writes complete: true for auto-completed conversations", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.members.set(GROUP_A, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member message from 4 hours ago (> completeHours=3h) → auto-complete + const oldCi = makeChatItem({dir: "groupRcv", text: "Resolved!", memberId: `team-${TEAM_MEMBER_1_ID}`, memberContactId: TEAM_MEMBER_1_ID}) + oldCi.meta.createdAt = new Date(Date.now() - 4 * 3600_000).toISOString() + chat.chatItems.set(GROUP_A, [oldCi]) + // Create initial card data + chat.customData.set(GROUP_A, {cardItemId: 500}) + + cards.scheduleUpdate(GROUP_A) + await cards.flush() + + const data = chat.customData.get(GROUP_A) + expect(data.complete).toBe(true) + }) + + test("card flush clears complete flag when conversation becomes active again", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.members.set(GROUP_A, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member message from 4h ago + recent customer message → NOT complete + const teamCi = makeChatItem({dir: "groupRcv", text: "Resolved!", memberId: `team-${TEAM_MEMBER_1_ID}`, memberContactId: TEAM_MEMBER_1_ID}) + teamCi.meta.createdAt = new Date(Date.now() - 4 * 3600_000).toISOString() + const custCi = makeChatItem({dir: "groupRcv", text: "Actually one more question", memberId: CUSTOMER_ID}) + chat.chatItems.set(GROUP_A, [teamCi, custCi]) + // Previously complete + chat.customData.set(GROUP_A, {cardItemId: 500, complete: true}) + + cards.scheduleUpdate(GROUP_A) + await cards.flush() + + const data = chat.customData.get(GROUP_A) + expect(data.complete).toBeUndefined() + }) + + test("refreshAllCards continues on individual card failure", async () => { + const GROUP_A = 101, GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.customData.set(GROUP_A, {cardItemId: 100}) + chat.customData.set(GROUP_B, {cardItemId: 200}) + + chat.apiDeleteChatItemsWillFail() + await cards.refreshAllCards() + expectCardDeleted(200) + }) +}) + +describe("joinedGroupMember Event Filtering", () => { + beforeEach(() => setup()) + + test("joinedGroupMember in non-team group → ignored (no DM)", async () => { + const member = {memberId: "someone", groupMemberId: 9000, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Someone"}} + await bot.onJoinedGroupMember(joinedEvent(CUSTOMER_GROUP_ID, member)) + expect(chat.rawCmds.length).toBe(0) + expect(chat.sent.filter(s => s.chat[0] === ChatType.Direct).length).toBe(0) + }) + + test("joinedGroupMember from wrong user → ignored", async () => { + const member = {memberId: "someone", groupMemberId: 9001, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Someone"}} + await bot.onJoinedGroupMember(joinedEvent(TEAM_GROUP_ID, member, GROK_USER_ID)) + expect(chat.rawCmds.length).toBe(0) + }) +}) + +describe("parseConfig Validation", () => { + const baseArgs = ["--team-group", "Support"] + + test("--complete-hours non-numeric → throws", () => { + expect(() => parseConfig([...baseArgs, "--complete-hours", "abc"])) + .toThrow(/--complete-hours must be a non-negative integer, got "abc"/) + }) + + test("postgres backend without --pg-conn → throws", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "postgres" + try { + expect(() => parseConfig(baseArgs)) + .toThrow(/--pg-conn is required when backend is postgres/) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("postgres backend with --pg-conn → db is postgres DbConfig", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "postgres" + try { + const cfg = parseConfig([...baseArgs, "--pg-conn", "postgres://user:pass@localhost/db"]) + expect(cfg.db).toEqual({type: "postgres", connectionString: "postgres://user:pass@localhost/db"}) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("postgres backend with --pg-schema → DbConfig carries schemaPrefix", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "postgres" + try { + const cfg = parseConfig([...baseArgs, "--pg-conn", "postgres://localhost/db", "--pg-schema", "bot"]) + expect(cfg.db).toEqual({type: "postgres", connectionString: "postgres://localhost/db", schemaPrefix: "bot"}) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("sqlite backend (default) → db is sqlite DbConfig with default filePrefix", () => { + const prevBackend = process.env.SIMPLEX_BACKEND + const prevNpm = process.env.npm_config_simplex_backend + delete process.env.SIMPLEX_BACKEND + delete process.env.npm_config_simplex_backend + try { + const cfg = parseConfig(baseArgs) + expect(cfg.db).toEqual({type: "sqlite", filePrefix: "./data/simplex"}) + } finally { + if (prevBackend !== undefined) process.env.SIMPLEX_BACKEND = prevBackend + if (prevNpm !== undefined) process.env.npm_config_simplex_backend = prevNpm + } + }) + + test("sqlite backend with --sqlite-key → DbConfig carries encryptionKey", () => { + const cfg = parseConfig([...baseArgs, "--sqlite-key", "secret"]) + expect(cfg.db).toEqual({type: "sqlite", filePrefix: "./data/simplex", encryptionKey: "secret"}) + }) + + test("unknown flag → parseArgs throws", () => { + expect(() => parseConfig([...baseArgs, "--team-gropu", "typo"])) + .toThrow() + }) + + test("missing --team-group → throws", () => { + expect(() => parseConfig([])) + .toThrow(/required option '--team-group/) + }) + + test("invalid SIMPLEX_BACKEND → throws", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "mysql" + try { + expect(() => parseConfig(baseArgs)) + .toThrow(/Invalid SIMPLEX_BACKEND: "mysql"/) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("--complete-hours negative → throws", () => { + // parseArgs refuses "-1" as a bare arg (ambiguous with a short flag), so use `=` form + expect(() => parseConfig([...baseArgs, "--complete-hours", "-1"])) + .toThrow(/--complete-hours must be a non-negative integer, got "-1"/) + }) + + test("--card-flush-seconds non-numeric → throws", () => { + expect(() => parseConfig([...baseArgs, "--card-flush-seconds", "xyz"])) + .toThrow(/--card-flush-seconds must be a non-negative integer, got "xyz"/) + }) + + test("--timezone invalid IANA → throws", () => { + expect(() => parseConfig([...baseArgs, "--timezone", "Not/AZone"])) + .toThrow(/--timezone "Not\/AZone" is not a valid IANA time zone/) + }) + + test("--complete-hours 0 → allowed (disables auto-complete)", () => { + const cfg = parseConfig([...baseArgs, "--complete-hours", "0"]) + expect(cfg.completeHours).toBe(0) + }) + + test("valid IANA timezone → accepted", () => { + const cfg = parseConfig([...baseArgs, "--timezone", "America/New_York"]) + expect(cfg.timezone).toBe("America/New_York") + }) +}) + +describe("GrokApiClient HTTP timeout", () => { + test("chat() calls AbortSignal.timeout(60_000) and passes the signal to fetch", async () => { + const timeoutSpy = vi.spyOn(AbortSignal, "timeout") + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response(JSON.stringify({choices: [{message: {content: "ok"}}]}), {status: 200}), + ) + + const client = new GrokApiClient("test-key", "system prompt") + await client.chat([], "hello") + + expect(timeoutSpy).toHaveBeenCalledWith(60_000) + expect((fetchSpy.mock.calls[0][1] as RequestInit).signal).toBeInstanceOf(AbortSignal) + + fetchSpy.mockRestore() + timeoutSpy.mockRestore() + }) +}) + +// Lazy per-group command sync. sendToGroup always calls +// apiUpdateGroupProfile on the first send per group when the group's +// stored groupPreferences.commands don't match desiredCommands. Each +// group is synced at most once per process (cache hit on subsequent +// sends). +describe("Command sync in sendToGroup", () => { + beforeEach(() => setup()) + + test("first send → apiUpdateGroupProfile called once with merged commands", async () => { + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Hello, just a greeting.") + expect(chat.profileUpdates).toHaveLength(1) + const {groupId, profile} = chat.profileUpdates[0] + expect(groupId).toBe(CUSTOMER_GROUP_ID) + expect(profile.groupPreferences.commands).toEqual(DESIRED_COMMANDS) + // Existing groupProfile fields (displayName, fullName) are preserved. + expect(profile.displayName).toBe(`Group${CUSTOMER_GROUP_ID}`) + expect(profile.fullName).toBe("") + // The actual message still goes out after the sync. + expect(chat.lastSentTo(CUSTOMER_GROUP_ID)).toBe("Hello, just a greeting.") + }) + + test("group already has desired commands → no apiUpdateGroupProfile, but still cached", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID) + gi.groupProfile.groupPreferences = {commands: DESIRED_COMMANDS} + chat.groups.set(CUSTOMER_GROUP_ID, gi) + + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok for help.") + expect(chat.profileUpdates).toHaveLength(0) + // Cache was populated — a subsequent send even against a divergent DB + // won't re-check. + gi.groupProfile.groupPreferences = {commands: []} + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Send /team for a human.") + expect(chat.profileUpdates).toHaveLength(0) + }) + + test("cache: two sends to same group → sync only once", async () => { + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok first.") + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Or send /team.") + expect(chat.profileUpdates).toHaveLength(1) + expect(chat.sentTo(CUSTOMER_GROUP_ID)).toHaveLength(2) + }) + + test("independent per group: different groups each sync separately", async () => { + const gId2 = 101 + chat.groups.set(gId2, makeGroupInfo(gId2)) + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok.") + await bot.sendToGroup(gId2, "Send /team.") + expect(chat.profileUpdates.map(p => p.groupId).sort()).toEqual([CUSTOMER_GROUP_ID, gId2].sort()) + }) + + test("merge preserves existing group preference fields (files, etc.)", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID) + gi.groupProfile.groupPreferences = { + files: {enable: "on"}, + reactions: {enable: "on"}, + } + chat.groups.set(CUSTOMER_GROUP_ID, gi) + + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok.") + expect(chat.profileUpdates).toHaveLength(1) + const prefs = chat.profileUpdates[0].profile.groupPreferences + expect(prefs.commands).toEqual(DESIRED_COMMANDS) + expect(prefs.files).toEqual({enable: "on"}) + expect(prefs.reactions).toEqual({enable: "on"}) + }) +}) diff --git a/apps/simplex-support-bot/package-lock.json b/apps/simplex-support-bot/package-lock.json new file mode 100644 index 0000000000..1569c18309 --- /dev/null +++ b/apps/simplex-support-bot/package-lock.json @@ -0,0 +1,2022 @@ +{ + "name": "simplex-chat-support-bot", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simplex-chat-support-bot", + "version": "0.1.0", + "license": "AGPL-3.0", + "dependencies": { + "@simplex-chat/types": "^0.5.0", + "async-mutex": "^0.5.0", + "commander": "^14.0.3", + "simplex-chat": "^6.5.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.9.3", + "vitest": "^1.6.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simplex-chat/types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@simplex-chat/types/-/types-0.5.0.tgz", + "integrity": "sha512-f680CRlf+O8WfIaPb7wxVj3PB8mTIOE+HqmetCSe0NBheVAjU3ovg3+zkrWwDlavrHuCLbb7Gmeu4HyNtjDfog==", + "license": "AGPL-3.0", + "dependencies": { + "typescript": "^5.9.2" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simplex-chat": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/simplex-chat/-/simplex-chat-6.5.0.tgz", + "integrity": "sha512-QFGI734HhYJ7trSrEKiZ2mbodI0V8CLDGEv2+yt5zsg0FqftxSpFik6zUSezTRZtN1M8WmSlT44qlEt2a1fXQw==", + "hasInstallScript": true, + "license": "AGPL-3.0", + "dependencies": { + "@simplex-chat/types": "^0.5.0", + "extract-zip": "^2.0.1", + "fast-deep-equal": "^3.1.3", + "node-addon-api": "^8.5.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/apps/simplex-support-bot/package.json b/apps/simplex-support-bot/package.json new file mode 100644 index 0000000000..ee57762465 --- /dev/null +++ b/apps/simplex-support-bot/package.json @@ -0,0 +1,23 @@ +{ + "name": "simplex-chat-support-bot", + "version": "0.1.0", + "private": true, + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@simplex-chat/types": "^0.5.0", + "async-mutex": "^0.5.0", + "commander": "^14.0.3", + "simplex-chat": "^6.5.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.9.3", + "vitest": "^1.6.1" + }, + "author": "SimpleX Chat", + "license": "AGPL-3.0" +} diff --git a/apps/simplex-support-bot/plans/20260207-support-bot-implementation.md b/apps/simplex-support-bot/plans/20260207-support-bot-implementation.md new file mode 100644 index 0000000000..c3c11ef61f --- /dev/null +++ b/apps/simplex-support-bot/plans/20260207-support-bot-implementation.md @@ -0,0 +1,1471 @@ +# SimpleX Support Bot — Implementation Plan + +## 1. Executive Summary + +SimpleX Chat support bot — standalone Node.js app using `simplex-chat-nodejs` native NAPI binding. Single `ChatApi` instance with two user profiles (main bot + Grok agent) sharing one SQLite database. A `profileMutex` serializes all profile-switching + SimpleX API calls. Team sees active conversations as cards in a dashboard group — no text forwarding. Implements flow: Welcome → Queue → Grok/Team-Pending → Team. + +## 2. Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Support Bot Process (Node.js) │ +│ │ +│ chat: ChatApi ← ChatApi.init("./data/simplex") │ +│ Single database, two user profiles │ +│ │ +│ mainUserId ← non-Grok user (default name: │ +│ "Ask SimpleX Team") │ +│ • Business address, event routing, state mgmt │ +│ • Controls group membership │ +│ │ +│ grokUserId ← "Grok" profile │ +│ • Joins customer groups as Member │ +│ • Sends Grok responses into groups │ +│ │ +│ profileMutex: serialize apiSetActiveUser + call │ +│ GrokApiClient → api.x.ai/v1/chat/completions │ +└─────────────────────────────────────────────────┘ +``` + +- Single Node.js process, single `ChatApi` instance via native NAPI +- Two user profiles in one database. The main profile is returned directly from `bot.run()`. The Grok profile's `userId` is persisted to `state.json` as `grokUserId` on the first run (when the bot creates it); subsequent runs identify Grok strictly by that persisted ID (never by display name, which a rename would invalidate). The main profile's displayName is set only on fresh-DB user creation (`"Ask SimpleX Team"`) and is never rewritten by bot code thereafter — `bot.run()` is invoked with `updateProfile: false`. Bot commands (`/grok`, `/team`) are never pushed via global `apiUpdateProfile`; instead they sync lazily per-group in `sendToGroup` — the first send to each group triggers `syncGroupCommands(groupId)`, which verifies the group's `groupPreferences.commands` against `desiredCommands` and calls `apiUpdateGroupProfile` if different (scoped broadcast to that group's members only). Subsequent sends to the same group are cache hits. +- `profileMutex` serializes `apiSetActiveUser(userId)` + the subsequent SimpleX API call. Grok HTTP API calls run **outside** the mutex. +- Events delivered for all profiles — routed by `event.user` field (main → main handler, Grok → Grok handler) +- Business address auto-accept creates a group per customer +- Grok is a second profile invited as a Member — appears as a separate participant +- No cross-profile ID mapping needed — Grok profile uses its own local group IDs from its own events + +## 3. Project Structure + +``` +apps/simplex-support-bot/ +├── package.json # deps: simplex-chat, @simplex-chat/types, async-mutex; devDeps: vitest, @types/node +├── tsconfig.json # ES2022, strict, Node16 module resolution +├── vitest.config.ts # test runner config, path aliases for mocks +├── src/ +│ ├── index.ts # Entry: parse config, init instance, run +│ ├── config.ts # CLI arg parsing, ID:name validation, Config type +│ ├── bot.ts # SupportBot class: state derivation, event dispatch, cards +│ ├── cards.ts # Card formatting, debouncing, lifecycle +│ ├── grok.ts # GrokApiClient: xAI API wrapper, system prompt, history +│ ├── messages.ts # All user-facing message templates +│ └── util.ts # isWeekend, profileMutex, logging helpers +├── bot.test.ts # Vitest suite (154 tests, 31 describes) +├── test/ +│ └── __mocks__/ +│ ├── simplex-chat.js # MockChatApi + utility re-exports +│ └── simplex-chat-types.js # enum re-exports for tests +└── data/ # SQLite databases (created at runtime) +``` + +The Grok system-prompt / context file is supplied at runtime via `--context-file ` (see §4). It is not part of the repo tree. + +## 4. Configuration + +**CLI flags:** + +| Flag | Required | Default | Format | Purpose | +|------|----------|---------|--------|---------| +| `--db-prefix` | No | `./data/simplex` | path | Database file prefix (both profiles share it) | +| `--team-group` | Yes | — | `name` | Team group display name (auto-created if absent, resolved by persisted ID on restarts) | +| `--auto-add-team-members` / `-a` | No | `""` | `ID:name,...` | Comma-separated team member contacts. Validated at startup — exits on mismatch. | +| `--context-file` | Required when `GROK_API_KEY` set | — | path | Grok system-prompt file (SimpleX documentation context). `parseConfig` throws if `GROK_API_KEY` is set without this flag. | +| `--timezone` | No | `"UTC"` | IANA tz | For weekend detection (24h vs 48h). Weekend = Sat 00:00 – Sun 23:59 in this tz. `parseConfig` validates the value by constructing a probe `Intl.DateTimeFormat` and throws with a clear error on `RangeError` (invalid IANA zone) — bot exits before init. | +| `--complete-hours` | No | `3` | integer ≥ 0 | Hours of customer inactivity after last team/Grok reply before auto-completing a conversation (✅). `parseConfig` rejects non-numeric, negative, or `NaN` values with a fail-fast error. `0` is allowed and disables auto-complete. | +| `--card-flush-seconds` | No | `300` | integer ≥ 0 | Seconds between card dashboard update flushes. `parseConfig` rejects non-numeric, negative, or `NaN` values with a fail-fast error. `0` is allowed and disables periodic flush (card updates still occur on explicit `scheduleUpdate` callers but never auto-drain). | + +**Env vars:** `GROK_API_KEY` (optional) — xAI API key. If unset or empty, the bot starts with Grok support fully disabled: it logs `"No GROK_API_KEY provided, disabling Grok support"`, skips Grok profile/contact setup and event handler registration, omits `/grok` from the bot command list, drops the `/grok` clause from customer-facing messages, and treats any `/grok` the customer still types as an unknown command. + +**Numeric argument validation:** `parseConfig` MUST validate every numeric flag (`--complete-hours`, `--card-flush-seconds`) using a helper that throws on non-finite or negative results, rather than raw `parseInt`: + +```typescript +function parseNonNegativeInt(raw: string, flag: string): number { + const n = parseInt(raw, 10) + if (!Number.isFinite(n) || n < 0) { + throw new Error(`${flag} must be a non-negative integer, got "${raw}"`) + } + return n +} + +const completeHours = parseNonNegativeInt(optionalArg(args, "--complete-hours", "3"), "--complete-hours") +const cardFlushSeconds = parseNonNegativeInt(optionalArg(args, "--card-flush-seconds", "300"), "--card-flush-seconds") +``` + +Rationale: `parseInt("foo", 10)` returns `NaN`, and `NaN * 3600_000 === NaN`. Every subsequent comparison (`now - lastTeamGrokTime >= completeMs`) is `false`, so the feature silently becomes a no-op — auto-complete never fires, cards never auto-refresh — and the operator has no signal that they typo'd a flag. Failing fast at startup surfaces the typo before customers interact. `0` is explicitly allowed as a valid "disable" setting. + +**Timezone validation:** `parseConfig` MUST validate `--timezone` by constructing a probe `Intl.DateTimeFormat`: + +```typescript +try { + new Intl.DateTimeFormat("en-US", {timeZone: timezone, weekday: "short"}) +} catch (err) { + throw new Error(`--timezone "${timezone}" is not a valid IANA time zone: ${(err as Error).message}`) +} +``` + +Rationale: `isWeekend` is called from `queueMessage` and `teamAddedMessage` — both run on the hot customer message path. `new Intl.DateTimeFormat(..., {timeZone: , ...})` throws `RangeError: Invalid time zone specified` at every call. Without startup validation, a typo in `--timezone` turns every `/grok`, `/team`, or first-customer-message dispatch into an unhandled error that crashes the per-item handler (though the outer try/catch in `onNewChatItems` contains it, customers receive no reply at all). Validating once at startup surfaces the typo in the operator's console before any customer interaction. + +```typescript +interface Config { + dbPrefix: string + teamGroup: {id: number; name: string} // id=0 at parse time, resolved at startup + teamMembers: {id: number; name: string}[] + grokContactId: number | null // always restored from state file at startup (even when Grok API is disabled, so the one-way gate can identify and remove Grok members) + timezone: string + completeHours: number // default 3 + cardFlushSeconds: number // default 300 + contextFile: string | null // path to Grok system-prompt file; required when grokApiKey !== null + grokApiKey: string | null // null when GROK_API_KEY is not set → Grok disabled +} +``` + +**State file** — `{dbPrefix}_state.json` (co-located with DB files): +```json +{"teamGroupId": 123, "grokContactId": 4} +``` + +Only two keys. All other state is persisted in the group's `customData` (per-conversation state, card IDs) or derived from group metadata (`apiListMembers`). Display data like message counts is read from chat history on demand. + +**Grok contact resolution** (state-file lookup always runs; contact establishment only when enabled): +1. Read `grokContactId` from state file → validate via `apiListContacts` → set `config.grokContactId` (this always runs, even when `grokApiKey === null`, so the one-way gate can identify and remove Grok members from groups) +2. If not found and `grokEnabled`: main profile creates one-time invite link, Grok profile connects, wait for a `contactConnected` event filtered by profile identity (60s — see "Grok contact identification" below), persist the resulting `contactId` atomically before proceeding. +3. If unavailable (with Grok otherwise enabled), bot runs but `/grok` returns "temporarily unavailable" +4. If `grokApiKey === null`: the Grok profile is not resolved or created, no invite link is issued — but `config.grokContactId` is still set from the state file if the contact exists. + +### Grok contact identification + +`grokContactId` is written once and used forever — it is the single identifier for every subsequent Grok check (one-way gate, `onMemberConnected` skip, `isGrok` in card rendering). Identification MUST be narrowly scoped so that the `contactId` stored is unambiguously Grok's and no other contact completing a handshake in the 60s establishment window can be latched by mistake. + +Use the predicate form of `ChatApi.wait`. The signature (defined in `node_modules/simplex-chat/src/api.ts:217`) is: + +```typescript +wait( + event: K, + predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined, + timeout: number, +): Promise +``` + +The implementation (api.ts:234) keeps the subscriber attached when the predicate returns `false`, so non-matching events are silently discarded and the wait continues until a matching event arrives or the timeout fires. + +Identification accepts only a `contactConnected` event observed by the MAIN profile (the profile whose `apiCreateLink` issued the invite, and whose `contactId` we persist and later pass to `apiAddMember`) whose connecting contact's profile `displayName` equals the Grok profile's displayName: + +```typescript +const grokProfileName = grokUser.profile.displayName // "Grok" (canonical) +const evt = await chat.wait( + "contactConnected", + (e) => + e.user.userId === mainUser.userId && + e.contact.profile.displayName === grokProfileName, + 60_000, +) +if (!evt) { + console.error(`Timeout waiting for Grok contact (60s, displayName="${grokProfileName}"). ` + + `Check SMP relay availability or re-run after clearing state. Exiting.`) + process.exit(1) +} +config.grokContactId = evt.contact.contactId +state.grokContactId = config.grokContactId +writeState(stateFilePath, state) // atomic: tmp-file + rename (see §13 state persistence) +log(`Grok contact established: ID=${config.grokContactId} (displayName="${grokProfileName}")`) +``` + +Filter rationale: +- `e.user.userId === mainUser.userId` selects the main profile's view of the handshake. Both profiles observe the handshake (the Grok-side event describes the main profile as the `contact`); only the main-side event carries the `contactId` we need for subsequent `apiAddMember` calls. +- `e.contact.profile.displayName === grokProfileName` accepts only the contact whose profile matches the Grok profile just created/updated. This rejects stray inbound contacts (late business-request acceptance, operator test DM, a reconnect of an existing contact) that may complete in the same 60s window. The displayName is read from `evt.contact.profile`, which is `LocalProfile` (see `@simplex-chat/types/src/types.ts:2867`). + +`grokProfileName` is captured from `grokUser.profile.displayName` immediately before the wait, so whichever name the Grok profile was created/updated with earlier in startup is the exact string matched here. + +Single-tenant deployment caveat: if a human contact happens to set its SimpleX displayName to the literal `"Grok"` and completes a handshake with the main profile in the 60s window, the displayName filter alone cannot distinguish them. MVP is single-tenant and Grok's profile is created by the bot itself, so this is not expected in practice; deployments that need stronger guarantees can add a second filter (e.g. `e.contact.profile.image === grokImage` — the bot knows the exact image bytes it assigned to the Grok profile). + +Persistence: `writeState` is atomic (tmp-file + `fs.renameSync`, see §13 "State persistence") so a crash between identification and persistence cannot corrupt the state file. `state.grokContactId` is flushed to disk BEFORE proceeding to bot event wiring — if the process dies after wiring but before persistence, the next startup would issue a second invite link and leave the first Grok contact orphaned in the database. + +**Team group resolution** (auto-create): +1. Read `teamGroupId` from state file → validate via group list +2. If not found: create with `apiNewGroup`, persist new group ID +3. If found: compare `fullGroupPreferences` (directMessages, fullDelete, commands) and displayName with desired values. Only call `apiUpdateGroupProfile` if something differs — avoids unnecessary SMP relay round-trips on every restart. + +**Team group invite link lifecycle:** +1. Delete stale link (best-effort), create new link, print to stdout. Creation is best-effort — if the SMP relay is unreachable, the error is logged and the bot continues without an invite link. The 10-minute deletion timer is only scheduled if creation succeeded. +2. Delete after 10 minutes. On SIGINT/SIGTERM, delete before exit. Deletion must go through `profileMutex` with `apiSetActiveUser(mainUserId)` — the active user may be the Grok profile at the time the timer fires or the signal arrives. + +**Team member validation:** +- If `--auto-add-team-members` (`-a`) provided: validate each contact ID/name pair, fail-fast on mismatch +- If not provided: `/team` tells customers "no team members available yet" + +## 5. State Derivation (Stateless) + +Per-conversation state is stored in the group's `customData` and written at the moment the bot handles each transition (customer's first message, `/grok`, `/team`, team member's first message). On subsequent events `deriveState` returns the stored state as-is — composition changes (team members leaving, Grok leaving) do **not** demote the stored state. The customer's mode (e.g. "waiting for a team response") is meaningful even when no team member is currently present; keeping the state preserves that. Composition is read only by specific handlers (e.g. the `/team` duplicate-invite guard). No chat-history scans for state decisions. No in-memory conversations map — survives restarts. + +**WELCOME detection:** customData has no `state` field until the bot handles the first transition. `deriveState` returns `WELCOME` precisely when `customData.state` is absent. + +**Type vs. persisted state.** The `ConversationState` union in `cards.ts` enumerates all five conceptual states (`WELCOME | QUEUE | GROK | TEAM-PENDING | TEAM`) so event handlers and composition can reason about them uniformly. However, `WELCOME` is NEVER written to `customData.state` — the runtime invariant is "persisted state ∈ {QUEUE, GROK, TEAM-PENDING, TEAM}; absence of the `state` field derives as WELCOME". The `isConversationState` guard in `cards.ts` rejects `WELCOME` on read to preserve this invariant (any stale `state: "WELCOME"` from a crashed transition is treated as absent). Do NOT introduce a separate `PersistedState` type in MVP — the invariant is small enough to enforce at two choke points: `getRawCustomData` on read and the dispatch handlers on write. + +**State-write matrix:** + +| Bot-observed event | `customData.state` written | +|---|---| +| *(initial — no customData yet)* | *(absent ⇒ WELCOME)* | +| Customer's first non-command message | `QUEUE` | +| `/grok` handled — Grok invited | `GROK` | +| `/team` handled — team members added (written at handler time; does not wait for team acceptance) | `TEAM-PENDING` | +| First team-member text message observed | `TEAM` | + +**State is authoritative and monotonic.** Once written, `customData.state` persists across member leave/join events. The only path that clears it is the existing `onLeftMember` handler when the customer themselves leaves — at that point the entire customData is cleared. + +**Failure-path revert is CAS-guarded.** `activateGrok` runs fire-and-forget, so its `setStateOnFail` revert (`QUEUE`) can race with a concurrent transition (e.g. `/team` writing `TEAM-PENDING` while `waitForGrokJoin` is pending). To preserve monotonicity, `revertStateOnFail` is a compare-and-set: it only writes `setStateOnFail` if `customData.state === "GROK"` (the optimistic value both call sites write before invoking `activateGrok`). If another handler has since stamped a different state, the revert is skipped — the in-flight transition wins and stays. + +TEAM-PENDING takes priority over GROK when both Grok and team are present (after `/team` but before team member's first message). `/grok` remains available in TEAM-PENDING — if Grok is not yet in the group, it gets invited; if already present, the command is ignored. + +**State derivation helpers:** +- `getGroupComposition(groupId)` → `{grokMember, teamMembers}` from `apiListMembers` — used for card rendering and the `/team` duplicate-invite guard. +- `deriveState(groupId)` → reads `customData.state`. Returns `WELCOME` iff `customData.state` is absent. No composition lookup. +- `getLastCustomerMessageTime(groupId)` / `getLastTeamOrGrokMessageTime(groupId)` → chat-history timestamp reads used by the card renderer for wait-time and auto-complete only (display, not state). + +**Transitions:** +``` +WELCOME ──(1st msg)──────> QUEUE (send queue msg, create card 🆕) +WELCOME ──(/grok 1st)────> GROK (skip queue msg, create card 🤖) +WELCOME ──(/team 1st)────> TEAM-PENDING (skip queue msg, add team members, create card 👋) +QUEUE ──(/grok)──────────> GROK (invite Grok, update card) +QUEUE ──(/team)──────────> TEAM-PENDING (add team members, update card) +GROK ──(/team)───────────> TEAM-PENDING (add all team members, Grok stays, update card) +GROK ──(user msg)────────> GROK (Grok responds, update card) +TEAM-PENDING ──(/grok)───> invite Grok if not present, else ignore (state stays TEAM-PENDING) +TEAM-PENDING ──(/team)───> reply "already invited" (if team members still present; else re-add silently) +TEAM-PENDING ──(team msg)> TEAM (remove Grok, disable /grok permanently, update card) +TEAM ──(/grok)───────────> reply "team mode", stay TEAM +``` + +## 6. Card-Based Dashboard + +The team group is a live dashboard. The bot maintains exactly one message ("card") per active customer conversation. Cards are deleted and reposted on changes — the group is always a current snapshot. + +### Card format + +Card is a single message. The join command is the final line of the card text — there is no separate join message. + +``` +[ICON] *[Customer Name]* · [wait] · [N msgs] +[STATE][· agent1, agent2, ...] +"[last message(s), truncated]" +/'join [id]' +``` + +**Icons:** + +| Icon | Condition | +|------|-----------| +| 🆕 | QUEUE — first message < 5 min ago | +| 🟡 | QUEUE — waiting < 2 h | +| 🔴 | QUEUE — waiting > 2 h | +| 🤖 | GROK — Grok handling | +| 👋 | TEAM — team added, no reply yet | +| 💬 | TEAM — team has replied, conversation active (customer replied after team) | +| ⏰ | TEAM — customer follow-up unanswered > 2 h | +| ✅ | Done — no customer reply for `completeHours` (default 3h) after last team/Grok message | + +**State labels:** `Queue`, `Grok`, `Team – pending`, `Team` + +**Agents:** comma-separated display names of team members in the group. Omitted when none. + +**Message count:** All messages in chat history except the bot's own (`groupSnd` from main profile). + +**Message preview:** Last several messages, most recent last, separated by ` / `. Newlines in message text are replaced with spaces to prevent card layout bloat from spam. The customer's display name is sanitized (newlines → spaces) for the card header; the `/join` command embeds only the numeric group id. Newest messages are prioritized — when the total exceeds ~500 chars (`maxTotal = 500` in `composeCard`), the oldest messages are truncated (with `[truncated]` prepended) while the newest are always shown. When truncation occurs, the first visible message is guaranteed to have a sender prefix even if it was a continuation in the original sequence. Each message is prefixed with the sender's name (`Name: message`) on the first message in a consecutive run from that sender - subsequent messages from the same sender omit the prefix until a different sender's message appears. Sender identification: Grok contact is detected by `grokContactId` and labeled "Grok"; the customer is identified by matching `memberId` to the group's `customerId` and labeled with their display name; all other members use their `memberProfile.displayName`. Bot's own messages (`groupSnd`) are excluded. Each message truncated to ~200 chars. Media-only messages show type labels: `[image]`, `[file]`, `[voice]`, `[video]`. + +**Join command:** the final line of the card renders as `/'join '` where `` is the customer group's numeric ID. The outer single quotes around `join ` are rendered by SimpleX clients as a clickable quoted command; tapping it sends `/join ` back to the team group. The handler does not pattern-match the message text — it uses the framework's structured command parser (`util.ciBotCommand`) which returns `{keyword: "join", params: ""}` directly from the chat item. The handler then converts `params` to an integer via `Number.parseInt(params, 10)` and rejects anything that is not a positive integer. There is no legacy `/join :` form — the card never emits it, so the handler never needs to strip it. + +### Card lifecycle + +**Tracking:** `{state, cardItemId, complete?}` stored in customer group's `customData` via `apiSetGroupCustomData`. `state` is the canonical conversation state (`QUEUE | GROK | TEAM-PENDING | TEAM`); `cardItemId` is the team-group chat item ID for the (single) card message; `complete` flags the auto-completed state. Absence of `state` means WELCOME. Written at event time by the dispatch handlers — `/grok` handler writes `GROK` on invite; `/team` handler writes `TEAM-PENDING` immediately (does not wait for team acceptance); first observed team-member text message writes `TEAM`; first customer text message writes `QUEUE`. Read back from `groupInfo.customData` — single source of truth, survives restarts. All writes go through `CardManager.mergeCustomData` to preserve fields across independent write paths. + +**Create** — on first customer message (→ QUEUE) or `/grok` as first message (→ GROK): +1. Compose card text (including the `/'join '` final line) +2. Post it via `apiSendMessages(chatRef, [{msgContent: {type: "text", text}, mentions: {}}])` → get one `chatItemId`. The card is a single message; the `/'join '` line is clickable because SimpleX clients render the slash-prefixed single-quoted token as a clickable command even inside a multi-line message. +3. Write `{cardItemId}` to customer group's `customData` + +**Update** (delete + repost) — on every subsequent event (new customer msg, team/Grok reply, state change, agent join): +1. Read `{cardItemId}` from `customData` +2. Delete old card via `apiDeleteChatItems([Group, teamGroupId], [cardItemId], "broadcast")`. Per `simplex-chat/src/api.ts:436-445` the call either returns `T.ChatItemDeletion[]` (possibly empty if the item no longer exists) or throws `ChatCommandError`. Both outcomes are acceptable: the surrounding `try { ... } catch { /* log and continue */ }` allows execution to proceed whether the item was still present, already gone, or the server returned a transient error. +3. Post new card as a single message via `apiSendMessages` → get new `cardItemId`. **On failure** the partial-failure policy below applies: log, re-queue this groupId into `pendingUpdates`, return without writing `customData`. +4. Write `{cardItemId, complete?}` to `customData` via `mergeCustomData`. **On failure** the tracking-write policy below applies. + +**Debouncing:** Card updates debounced globally — pending changes flushed every `cardFlushSeconds` seconds (default 300, configurable via `--card-flush-seconds`). Within a batch, each group's card reposted at most once with latest state. + +**Wait time rules:** Time since the customer's last unanswered message. For ✅ (auto-completed) conversations, the wait field shows the literal string "done". If customer sends a follow-up, wait time resets to count from that message. + +**Auto-complete:** A conversation is marked ✅ when `completeHours` (default 3h, configurable via `--complete-hours`) have passed since the last team/Grok message **without any customer reply**. The card debounce flush (every 300 seconds / 5 min, configurable via `--card-flush-seconds`) checks elapsed time and transitions to ✅ when the threshold is met. Customer follow-up at any point — including after ✅ — reverts to the derived active icon (👋/💬/⏰ for team states, 🟡/🔴 for queue), and wait time resets from that message. + +**Card icon derivation (TEAM states) — computed at each card render by comparing the timestamps of the most recent customer and team/Grok messages in the group; nothing about the icon is stored:** +``` +Team added, no reply yet → 👋 +Team replied → 💬 +Customer follow-up unanswered >2h → ⏰ +No customer reply for completeHours → ✅ +Customer sends after ✅ → back to 💬 or ⏰ (derived from wait time) +``` + +**Cleanup** — customer leaves: card remains (TBD retention), clear `customData`. + +**Restart recovery:** On startup, `CardManager.refreshAllCards()` lists all groups, finds those with `customData.cardItemId` set and `customData.complete` not set, sorts by `cardItemId` ascending (higher ID = more recently updated), and re-posts them oldest-first so the most recently active cards end up at the bottom of the team group. Completed cards (`complete: true`) and old/pre-bot groups (no `customData`) are skipped. Old card messages are deleted before reposting; deletion failures (e.g., >24h old) are silently ignored. Individual card failures are caught and logged without aborting the batch. + +### Partial-failure and retry policy + +`createCard` and `updateCard` perform a multi-step sequence (delete + send + customData write). To design the correct policy we MUST be explicit about which failures the SimpleX core already handles for us vs. which surface to the bot: + +**SimpleX core semantics** (per `simplex-chat/src/api.ts` JSDoc): +- `apiSendMessages` — "Network usage: background". The call returns `newChatItems` once the chat item is CREATED LOCALLY (written to SQLite) and the SMP broadcast is QUEUED. The core's background machinery retries relay delivery transparently — **the bot never observes a transient relay failure from `apiSendMessages`**. A thrown `ChatCommandError` means the local create step itself failed: permission denied, chat does not exist, invalid content, DB locked/corrupted. +- `apiDeleteChatItems` — "Network usage: background". Same pattern: local delete + queued broadcast + core-managed delivery retry. A thrown error means the local delete step failed (item not found, permission, DB error). +- `apiSetGroupCustomData` — "Network usage: **no**". Pure local SQLite write, no SMP involvement at all. A thrown error means a local DB error. + +Consequence: failures surfaced to the bot are **terminal local errors** (bad state, DB problem, permission change), not transient network blips. Retrying the same operation against the same DB/relay state will usually hit the same error. Retry value comes from the narrow slice of genuinely transient local conditions — a brief SQLite lock held by a concurrent write, a race with group-state mutation elsewhere in the same process — where the next attempt sees a different state. + +This reshapes the policy: the bot does not need aggressive retry for "network" reasons (core handles that), and compensating actions for customData-write failure are rarely useful (if the pure-local customData write fails, the retry's customData write will almost certainly fail for the same reason). The bot needs a light safety net: re-queue on any step failure, let the flush loop try again at most once per `cardFlushSeconds`, and on persistent failure accept that operator intervention is needed. + +Policy (applies to both `createCard` and `updateCard`): + +**Any step fails** — whether step 2 (delete), step 3 (send), or step 4 (customData write): +- Log via `logError` with `{groupId, step, err}` so the operator can diagnose the underlying cause (permission change, DB corruption, bot removed from team group, etc). +- Re-add `groupId` to `pendingUpdates` via `this.scheduleUpdate(groupId)`. +- Return. Do NOT attempt compensating actions (no compensating delete for tracking-write failure — the scenario where send succeeds locally but customData write fails requires the SQLite DB to be healthy-then-unhealthy between two synchronous calls in the same transaction window, which is not a realistic transient state; the retry path handles any resulting duplicate by reading the stale `cardItemId` and deleting it on the next update attempt). + +**Flush dispatch** — the current `flush` loop calls `updateCard` unconditionally and `updateCard` returns early when `customData.cardItemId` is unset. This silently drops the retry path for a failed `createCard` — the group is in `pendingUpdates` but nothing will ever create a card for it. Replace with a single `flushOne(groupId)` that reads `customData` once and dispatches to create or update: + +```typescript +private async flushOne(groupId: number): Promise { + const groupInfo = await this.getGroupInfo(groupId) + if (!groupInfo) return // group deleted + const customData = this.deriveCustomData(groupInfo) + if (customData.complete) return // ✅ conversations don't auto-repost + if (typeof customData.cardItemId === "number") { + await this.updateCard(groupId, groupInfo) + } else { + await this.createCard(groupId, groupInfo) + } +} + +async flush(): Promise { + const groups = [...this.pendingUpdates] + this.pendingUpdates.clear() + for (const groupId of groups) { + try { await this.flushOne(groupId) } + catch (err) { + logError(`flush failed for group ${groupId}`, err) + this.scheduleUpdate(groupId) // re-queue on any thrown error + } + } +} +``` + +Retry behavior for each failure point under this design: + +| Failure point | `customData` after failure | Retry's `flushOne` path | Retry outcome if condition cleared | +|---|---|---|---| +| `createCard` send fails | `cardItemId` absent | create-path | fresh card posted, `customData` written | +| `updateCard` delete fails | old `cardItemId` still set | update-path | delete retried (idempotent — see below) + send + write | +| `updateCard` send fails (delete succeeded) | old (now-deleted) `cardItemId` still set | update-path | delete retried against stale ID — tolerated (see below) — then send + write | +| `updateCard` write fails (send succeeded, duplicate may exist) | old `cardItemId` still set, new card orphaned in team group | update-path | delete retried against stale old ID — tolerated — new card posted, tracking written; **leaked** new card from the failed attempt persists until operator removes it | + +**Delete idempotency on retry** — `apiDeleteChatItems` against already-deleted IDs returns either an empty `ChatItemDeletion[]` or throws `ChatCommandError`. The step-2 `try { ... } catch { logError(...) }` swallows both; execution proceeds to step 3. Do NOT escalate a step-2 error to the partial-failure policy — that would create a retry loop for a permanent condition (items past the 24h deletion window will throw on every retry forever). + +**Persistent failures** — if the underlying condition is not transient (bot removed from team group, DB corruption, permission revoked), every retry hits the same error and the group stays in `pendingUpdates` indefinitely, logging at each flush. MVP accepts this — the operator-visible log stream makes the problem diagnosable. A bounded-retry-with-backoff-and-giveup strategy can be added later without changing the failure-point table above. + +### Card implementation + +```typescript +class CardManager { + private pendingUpdates = new Set() // groupIds with pending updates + private flushInterval: NodeJS.Timeout + + constructor(private chat: ChatApi, private config: Config, private mainUserId: number, + flushIntervalMs = 300 * 1000) { + this.flushInterval = setInterval(() => this.flush(), flushIntervalMs) + this.flushInterval.unref() + } + + scheduleUpdate(groupId: number): void { + this.pendingUpdates.add(groupId) + } + + async createCard(groupId: number, groupInfo: T.GroupInfo): Promise { + const {text} = await this.composeCard(groupId, groupInfo) + // Single-message card — the `/'join '` line is the final line of `text`. + const items = await this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + await this.chat.apiSetGroupCustomData(groupId, { + cardItemId: items[0].chatItem.meta.itemId, + }) + } + + async flush(): Promise { + const groups = [...this.pendingUpdates] + this.pendingUpdates.clear() + for (const groupId of groups) { + await this.updateCard(groupId) + } + } + + async refreshAllCards(): Promise { + const groups = await this.chat.apiListGroups(mainUserId) + const activeCards = groups + .filter(g => typeof g.customData?.cardItemId === "number" && !g.customData?.complete) + .map(g => ({groupId: g.groupId, cardItemId: g.customData.cardItemId})) + // Sort ascending by cardItemId (higher = more recently updated) + activeCards.sort((a, b) => a.cardItemId - b.cardItemId) + for (const {groupId} of activeCards) { + try { await this.updateCard(groupId) } + catch (err) { logError(`Startup card refresh failed for group ${groupId}`, err) } + } + } + + private async updateCard(groupId: number): Promise { + // Read customData via apiListGroups + const customData = ... // {cardItemId} from groupInfo.customData + if (!customData?.cardItemId) return + // Delete old card message + try { + await this.chat.apiDeleteChatItems(Group, teamGroupId, + [customData.cardItemId], "broadcast") + } catch {} // card may already be deleted + const {text, complete} = await this.composeCard(groupId, groupInfo) + const items = await this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + const data = { + cardItemId: items[0].chatItem.meta.itemId, + ...(complete ? {complete: true} : {}), + } + await this.chat.apiSetGroupCustomData(groupId, data) + } + + private async composeCard(groupId: number, groupInfo: T.GroupInfo): Promise<{text: string, complete: boolean}> { + // Icon, state, agents, preview (with sender-name prefixes), /'join ' — per spec format + // The final line of `text` is `/'join '` — clickable in SimpleX clients. + // buildPreview(chatItems, customerName, customerId) — prefixes each sender's first message in a run + // Preview messages joined with blue "/" separator: " !3 /! " (SimpleX markdown for blue colored text) + // Message text is escaped via escapeStyledMarkdown() before joining — inserts U+200B after "!" + // when followed by a color trigger (1-6,r,g,b,y,c,m,-) to prevent false markdown interpretation. + // No escape mechanism exists in the SimpleX markdown parser for "!" styled text. + // complete = (icon === "✅") + } +} +``` + +## 7. Bot Initialization + +**Main bot** uses `bot.run()` with `events` parameter: + +```typescript +let supportBot: SupportBot + +const [chat, mainUser, mainAddress] = await bot.run({ + profile: {displayName: "Ask SimpleX Team", fullName: "", image: supportImage}, + dbOpts: {dbFilePrefix: config.dbPrefix}, + options: { + addressSettings: { + businessAddress: true, + autoAccept: true, + welcomeMessage, + }, + commands: [ + {type: "command", keyword: "grok", label: "Ask Grok"}, + {type: "command", keyword: "team", label: "Switch to team"}, + ], + useBotProfile: true, + updateProfile: false, // bot code never rewrites displayName/image/etc. + }, + events: { + acceptingBusinessRequest: (evt) => supportBot?.onBusinessRequest(evt), + newChatItems: (evt) => supportBot?.onNewChatItems(evt), + chatItemUpdated: (evt) => supportBot?.onChatItemUpdated(evt), + chatItemReaction: (evt) => supportBot?.onChatItemReaction(evt), + leftMember: (evt) => supportBot?.onLeftMember(evt), + joinedGroupMember: (evt) => supportBot?.onJoinedGroupMember(evt), + connectedToGroupMember: (evt) => supportBot?.onMemberConnected(evt), + newMemberContactReceivedInv: (evt) => supportBot?.onMemberContactReceivedInv(evt), + contactConnected: (evt) => supportBot?.onContactConnected(evt), + contactSndReady: (evt) => supportBot?.onContactSndReady(evt), + }, +}) +``` + +Note: `/grok` and `/team` are passed in `options.commands` so `bot.run()` has a profile to use when `apiCreateActiveUser` is needed on a fresh DB, but since `updateProfile: false` is set, `bot.run()` never writes the profile on subsequent runs. The user profile's `preferences.commands` is intentionally not pushed globally at startup — broadcasting `XInfo` to every contact is not wanted. Instead, the `SupportBot` takes `desiredCommands` as a constructor argument and syncs commands lazily per-group: `sendToGroup` (`src/bot.ts`) always calls `syncGroupCommands(groupId)` before dispatching the message. That helper reads the group via `apiGetChat(Group, groupId, 0)` (local, no network), and if `groupPreferences.commands` differs from `desiredCommands`, issues `apiUpdateGroupProfile` with the merged profile. `apiUpdateGroupProfile` broadcasts `XGrpInfo`/`XGrpPrefs` to group members only (scoped to the chat audience). Already-synced groups are cached in `syncedGroups: Set` so subsequent sends skip the read entirely — the first send per group costs one local read; every later send is a cache hit. Earlier drafts used a regex on the outgoing text to skip the sync when no command keyword appeared; that optimization was removed because the cache already makes repeated syncs free and the parser was a fragile source of correctness bugs. `/join` is registered as a team group command separately — after team group is resolved, call `apiUpdateGroupProfile(teamGroupId, groupProfile)` with `groupPreferences` including the `/join` command definition. Customer sending `/join` in a customer group → treated as ordinary message (unrecognized command). + +**Grok profile** — resolved from same ChatApi instance. Grok is identified strictly by the `userId` persisted in `state.json`; there is no by-name fallback (a renamed profile would otherwise be silently mistaken): + +```typescript +let grokUser: T.User | null = null +if (state.grokUserId !== undefined) { + const users = await chat.apiListUsers() + grokUser = users.find(u => u.user.userId === state.grokUserId)?.user ?? null + if (!grokUser) { + throw new Error( + `Persisted Grok userId=${state.grokUserId} not found in DB. ` + + `Either restore the user or delete state.json to re-create Grok.` + ) + } +} else { + // First run: create Grok and persist its userId immediately. + grokUser = await chat.apiCreateActiveUser({displayName: "Grok", fullName: "", image: grokImage}) + // apiCreateActiveUser sets Grok as active — switch back to main + await chat.apiSetActiveUser(mainUser.userId) + state.grokUserId = grokUser.userId + writeState(stateFilePath, state) +} + +// Refresh Grok's profile if it has drifted from the canonical values. +const grokProfile = {displayName: "Grok", fullName: "", image: grokImage} +const current = util.fromLocalProfile(grokUser.profile) +if (current.image !== grokProfile.image || current.displayName !== grokProfile.displayName || current.fullName !== grokProfile.fullName) { + await chat.apiSetActiveUser(grokUser.userId) + await chat.apiUpdateProfile(grokUser.userId, grokProfile) + await chat.apiSetActiveUser(mainUser.userId) +} +``` + +**Profile mutex** — all SimpleX API calls go through: + +```typescript +import {Mutex} from "async-mutex" + +const profileMutex = new Mutex() + +async function withProfile(userId: number, fn: () => Promise): Promise { + return profileMutex.runExclusive(async () => { + await chat.apiSetActiveUser(userId) + return fn() + }) +} +``` + +Grok HTTP API calls are made **outside** the mutex to avoid blocking. + +**Per-group customData mutex** — `mergeCustomData` and `clearCustomData` must be serialized per customer group. `mergeCustomData` has two awaits (read via `getRawCustomData` → `apiListGroups`, then write via `apiSetGroupCustomData`); between them the event loop runs, so two concurrent async chains operating on the same `groupId` can both read the same snapshot, both produce a merged object, and the second write clobbers the first's patch. + +Concrete call sites that can overlap on one `groupId`: +- `processMainChatItem` writing `state` transitions (WELCOME→QUEUE, WELCOME→GROK, QUEUE→GROK, one-way gate →TEAM) +- `activateGrok`'s `revertStateOnFail` (fire-and-forget) racing with subsequent customer messages +- `activateTeam` writing `TEAM-PENDING` racing with `/grok` or another `/team` on the same group +- `CardManager.flush → updateCard` writing `{cardItemId, complete}` racing with dispatch writing `state` +- `createCard` writing `{cardItemId}` immediately after dispatch writes `state` + +The CAS-on-state inside `revertStateOnFail` guards only the `state` key — other keys (`cardItemId`, `complete`) can still be lost when spread from a stale snapshot. + +Implementation: + +```typescript +// In CardManager +private customDataMutexes = new Map() + +private getCustomDataMutex(groupId: number): Mutex { + let m = this.customDataMutexes.get(groupId) + if (!m) { m = new Mutex(); this.customDataMutexes.set(groupId, m) } + return m +} + +async mergeCustomData(groupId: number, patch: Partial): Promise { + return this.getCustomDataMutex(groupId).runExclusive(async () => { + const current = (await this.getRawCustomData(groupId)) ?? {} + const merged = {...current, ...patch} + for (const key of Object.keys(merged) as (keyof CardData)[]) { + if (merged[key] === undefined) delete merged[key] + } + await this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId, merged)) + }) +} + +async clearCustomData(groupId: number): Promise { + return this.getCustomDataMutex(groupId).runExclusive(() => + this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId)) + ) +} +``` + +Nesting rule: the per-group customData mutex is the **outer** lock; `profileMutex` (via `withMainProfile`) is the **inner** lock. Never acquire them in the opposite order, and never hold the customData mutex while calling an external (non-SimpleX) async function — this prevents cross-group deadlock and keeps the critical section short. + +Cleanup: entries in `customDataMutexes` are bounded by the number of customer groups. Removing the entry on `onLeftMember(customer)` is sufficient (the group's `customData` is also cleared at that point). Skip this refinement in MVP if acceptable — a long-running bot with many customers accumulates a few bytes per group. + +**Profile images:** Both profiles have base64-encoded JPEG profile pictures (128x128, quality 85, under the 12,500-char data URI limit enforced by iOS/Android clients) set via the `image` field in `T.Profile`. The images are defined as `data:image/jpg;base64,...` string constants in `index.ts`. The main profile image is passed to `bot.run()` which handles update-on-change automatically. The Grok profile image is passed to `apiCreateActiveUser()` on first run; on subsequent runs, the bot compares the current profile against the desired one using `util.fromLocalProfile()` and calls `apiUpdateProfile()` if any field differs — this sends the update to all Grok contacts. + +**Startup sequence:** +0. **Active user recovery + name preservation:** Two related safeguards. + + **(a) Active user recovery.** On restart, the active user may be Grok (if the previous run was killed mid-profile-switch). `bot.run()` uses `apiGetActiveUser()` and would then operate against Grok's `userId` as if it were the main user. Fix: when `state.grokUserId` is set (i.e. this is not the very first run), pre-init the DB with a temporary `ChatApi` and compare the active user's `userId` against `state.grokUserId`. If they match, `apiListUsers()` + `apiSetActiveUser()` to the single non-Grok user — throw loudly if zero or multiple candidates exist, rather than silently picking. Close the temporary `ChatApi` before `bot.run()` reopens it. Identification is by userId, never by display name; a renamed Grok profile would defeat name matching. + + **(b) Never rewrite the main profile.** The core auto-creates a preset contact named `"Ask SimpleX Team"` in every user's DB (`src/Simplex/Chat/Library/Internal.hs:2749`, exact name from commit `362bdc328` 2025-07-12). That collides with the bot's preferred main-profile displayName within the user's `display_names` namespace (`UNIQUE (user_id, local_display_name)`), so any attempt to rename the main profile to `"Ask SimpleX Team"` fails with `duplicateName`. Worse, `bot.run`'s internal `updateBotUserProfile` (`packages/simplex-chat-nodejs/dist/bot.js:176`) re-syncs image, preferences, and `contactLink` on every startup, and on a DB where `users.local_display_name` has drifted from `contact_profiles.display_name`, the fast path (`src/Simplex/Chat/Store/Profiles.hs:311`) silently rewrites the customer-facing `contact_profiles.display_name`. Fix: pass `options.updateProfile: false` to `bot.run()` so the bot code never calls `apiUpdateProfile` on its own initiative. Whatever displayName the CLI saw is what stays. + + **(c) Lazy per-group command sync.** The bot's command list (`/grok`, `/team`) is synced lazily and per-group, not globally. `sendToGroup` (in `src/bot.ts`) unconditionally calls `syncGroupCommands(groupId)` before dispatching the message. That helper uses `apiGetChat(Group, groupId, 0)` (local DB read, no network) to read the current `groupProfile.groupPreferences.commands`, and if it doesn't match `desiredCommands`, issues `apiUpdateGroupProfile` with the commands merged in. `apiUpdateGroupProfile` broadcasts `XGrpInfo`/`XGrpPrefs` to group members only — scoped to the chat audience, never the whole contact list. Groups confirmed in-sync are cached in `syncedGroups: Set` so the first send per group costs one local read; every later send is a cache hit. No `apiUpdateProfile` (global XInfo broadcast) is ever invoked by bot code. Earlier drafts gated the sync behind a regex match on the outgoing text (to skip the read when no `/keyword` appeared); that optimization was removed because the cache already made repeated syncs free and the parser was a fragile source of correctness bugs. +1. `bot.run()` → init ChatApi, create/resolve main profile (with profile image), business address. Print business address link to stdout. +2. Resolve Grok profile: if `state.grokUserId` is set, look it up by ID via `apiListUsers()` (throw if missing); otherwise create via `apiCreateActiveUser()` and persist the new `userId`. Then compare the resolved profile against the canonical `{displayName, fullName, image}` and call `apiUpdateProfile()` if anything changed — pushes to Grok's contacts. +3. Read `{dbPrefix}_state.json` for `teamGroupId` and `grokContactId` +4. Enable auto-accept DM contacts: `apiSetAutoAcceptMemberContacts(mainUser.userId, true)` +5. List contacts, resolve Grok contact (from state or auto-establish) +6. Resolve team group (from state or auto-create) +7. Ensure direct messages + delete for everyone enabled on team group (conditional — only updates profile if preferences or name differ from desired) +8. Create team group invite link (best-effort), schedule 10min deletion if created +9. Validate `--auto-add-team-members` (`-a`) if provided +10. Register Grok event handlers on `chat` (filtered by `event.user === grokUserId`) +10b. Refresh stale cards: `CardManager.refreshAllCards()` — lists all groups, skips those with `customData.complete` or no `customData.cardItemId`, sorts remaining by `cardItemId` ascending, re-posts oldest-first so newest cards land at the bottom of team group +11. On SIGINT/SIGTERM → `clearTimeout(inviteLinkTimer)` (noop if already deleted), `cards.destroy()` (stops the card-flush interval), `deleteInviteLink()` (profileMutex-gated `apiDeleteGroupLink`), `process.exit(0)`. Signal handler is reentrant-safe: an `inviteLinkDeleted` flag prevents double-deletion; `clearTimeout`/`clearInterval` are no-op on undefined. + +**Grok event registration** (same ChatApi, filtered by profile): + +```typescript +chat.on("receivedGroupInvitation", async (evt) => { + if (evt.user.userId !== grokUserId) return + supportBot?.onGrokGroupInvitation(evt) +}) +chat.on("newChatItems", async (evt) => { + if (evt.user.userId !== grokUserId) return + supportBot?.onGrokNewChatItems(evt) +}) +chat.on("connectedToGroupMember", (evt) => { + if (evt.user.userId !== grokUserId) return + supportBot?.onGrokMemberConnected(evt) +}) +``` + +## 8. Event Processing + +**Main profile event handlers:** + +| Event | Handler | Action | +|-------|---------|--------| +| `acceptingBusinessRequest` | `onBusinessRequest` | Enable file uploads + visible history on business group | +| `newChatItems` | `onNewChatItems` | Route: team group → handle `/join`; customer group → derive state, dispatch; direct message → reply with business address link | +| `chatItemUpdated` | `onChatItemUpdated` | Schedule card update | +| `leftMember` | `onLeftMember` | Customer left → cleanup, card remains. Grok left → cleanup. Team member left → revert if no message sent. | +| `joinedGroupMember` | `onJoinedGroupMember` | Team group joiner (link-join): initiate DM via `apiCreateMemberContact` + `apiSendMemberContactInvitation`. Fires for any member joining via group invite link. | +| `connectedToGroupMember` | `onMemberConnected` | In team group: send DM with contact ID (if not already sent by `onJoinedGroupMember`). In customer group: promote to Owner (unless customer or Grok). | +| `chatItemReaction` | `onChatItemReaction` | Team/Grok reaction in customer group → schedule card update (auto-complete) | +| `newMemberContactReceivedInv` | `onMemberContactReceivedInv` | Team group member DM contact received: send contact ID message immediately (dedup via `sentTeamDMs`) | +| `contactConnected` | `onContactConnected` | Deliver pending DM if queued (dedup via `sentTeamDMs`) | +| `contactSndReady` | `onContactSndReady` | Deliver pending DM if queued (dedup via `sentTeamDMs`) | + +**Grok profile event handlers:** + +| Event | Handler | Action | +|-------|---------|--------| +| `receivedGroupInvitation` | `onGrokGroupInvitation` | Look up `pendingGrokJoins`; if found, auto-accept via `apiJoinGroup`; if not found (race), buffer in `bufferedGrokInvitations` for `activateGrok` to drain | +| `connectedToGroupMember` | `onGrokMemberConnected` | Grok now fully connected — read last 100 msgs from own view, call Grok API, send initial response | +| `newChatItems` | `onGrokNewChatItems` | Batch dedup: collect last customer text message per group in the event. Skip groups with `grokInitialResponsePending` set (initial combined response in flight). For the selected message: read last 100 msgs, call Grok API, send response. Non-text (images, files, voice) → ignored by Grok (card update handled by main profile). | + +**Message routing in `onNewChatItems` (main profile):** + +```typescript +// For each chatItem: +// 1. Direct message (not group) → reply with business address link, stop +// 2. Team group (groupId === teamGroupId) → handle /join command +// 3. Skip non-business-chat groups +// 4. Skip groupSnd (own messages) +// 5. Identify sender via businessChat.customerId +// 6. Team member message → check if first team text (trigger one-way gate: remove Grok, disable /grok), schedule card update +// 7. Customer message → derive state, dispatch: +// - WELCOME: create card, send queue msg (or handle /grok first msg → WELCOME→GROK, skip queue) +// - QUEUE: /grok → invite Grok; /team → add ALL configured team members; else schedule card update +// - GROK: /team → add ALL configured team members (Grok stays); else schedule card update +// - TEAM-PENDING: /grok → invite Grok if not present, else ignore; /team → if team members still present, reply "already invited"; if all team members have left, re-add silently (state stays TEAM-PENDING); else no action +// - TEAM: /grok → reply "team mode"; else no action +``` + +## 9. One-Way Gate + +The gate is event-driven and persists its transitions. The initial `/team` guard reads `customData.state` AND group composition: if state is already `TEAM-PENDING`/`TEAM` **and** team members are still present, the bot replies `teamAlreadyInvitedMessage` without re-adding. If state is `TEAM-PENDING`/`TEAM` but all team members have left, the bot re-adds them (state stays `TEAM-PENDING`). The first-team-message detection writes `state: 'TEAM'` into customData at the moment the bot observes the message, then removes Grok and disables `/grok`. + +1. User sends `/team` → ALL configured `--auto-add-team-members` (`-a`) added to group (each promoted to Owner at invite time via `apiSetMembersRole`, re-asserted on connect as fallback) → Grok stays if present → TEAM-PENDING +2. Repeat `/team` → detected via `customData.state ∈ {TEAM-PENDING, TEAM}` **and team members still present** → reply with `teamAlreadyInvitedMessage`. If team members have since left, re-add them silently (state stays `TEAM-PENDING`). +3. `/grok` still works in TEAM-PENDING (if Grok not present, invite it; if present, ignore — Grok responds to customer messages) +4. Any team member sends first text message in customer group → **gate triggers**: + - Remove Grok from group (`apiRemoveMembers`) + - `/grok` permanently disabled → replies: "You are now in team mode. A team member will reply to your message." + - State = `TEAM` (written as `customData.state = 'TEAM'` at observation time) +5. Detection: in `onNewChatItems`, when sender is a team member and `customData.state !== 'TEAM'`, trigger the gate and write `state: 'TEAM'` via `mergeCustomData`. + +**Edge cases:** +- All team members leave before sending → state stays `TEAM-PENDING` (customer is still waiting for a response); sending `/team` re-adds them without the "already invited" reply. +- Team member leaves after sending → state stays `TEAM` (`customData.state` persists). Customer can send `/team` again to re-add team members. + +## 10. Grok Integration + +Grok is a **second user profile** in the same ChatApi instance. Self-contained: watches its own events, reads history from its own view, calls Grok HTTP API, sends responses. + +### Grok-disabled mode (no `GROK_API_KEY`) + +If `GROK_API_KEY` is unset or empty, `parseConfig` returns `grokApiKey: null` (via `process.env.GROK_API_KEY || null`, so `GROK_API_KEY=` is treated the same as unset; no throw) and `index.ts` derives `grokEnabled = config.grokApiKey !== null`. When `grokEnabled === false`: + +- Startup logs: `"No GROK_API_KEY provided, disabling Grok support"`. +- **`config.grokContactId` is still restored from the state file** (the lookup runs unconditionally before the `if (grokEnabled)` block). This ensures `getGroupComposition` can identify Grok members so the one-way gate can remove them when a team member sends a text message — even while Grok API is disabled. Without this, Grok members would become "phantom" members: physically present in groups but invisible to the state machine, preventing the gate from firing and causing dual responses (Grok + team) if Grok is later re-enabled. +- The Grok profile is not resolved or created (no `apiListUsers`/`apiCreateActiveUser` for "Grok"; no invite link issued). +- `GrokApiClient` is not instantiated. +- `SupportBot` receives `grokApi = null` and `grokUserId = null`. +- Bot command list registered at startup contains only `/team` — `/grok` is not advertised. +- Grok event handlers (`receivedGroupInvitation`, `connectedToGroupMember`, Grok-side `newChatItems`) are not registered. Handlers that are shared with the main profile (e.g. `onMemberConnected`) remain correct because their Grok checks are guarded by `this.config.grokContactId !== null`. +- Customer-facing messages (`queueMessage`, `noTeamMembersMessage`) accept a `grokEnabled` flag and drop the `/grok` clause when false. +- If the customer still types `/grok` manually, `processMainChatItem` rewrites `cmd` to `null` when `rawCmd?.keyword === "grok" && !this.grokEnabled`, so the dispatcher treats it as an unrecognized command (same as any other plain text). +- Defense in depth: `activateGrok` and `processGrokChatItem` short-circuit on entry when `this.grokApi === null`; `withGrokProfile` throws if called with `grokUserId === null`. + +Type signatures affected: +- `Config.grokApiKey: string | null` +- `SupportBot` constructor: `chat, grokApi: GrokApiClient | null, config, mainUserId, grokUserId: number | null, desiredCommands: T.ChatBotCommand[]` — `desiredCommands` is required (used by `sendToGroup`'s lazy per-group commands sync; see §20.4 suite 30 and the §7 "Note" describing `syncGroupCommands`). +- `queueMessage(timezone: string, grokEnabled: boolean): string` +- `noTeamMembersMessage(grokEnabled: boolean): string` (was a plain `const string`) + +### Grok join flow + +**Critical:** `activateGrok` awaits `waitForGrokJoin(120s)` which depends on future events dispatched through the same sequential event loop (`runEventsLoop` in api.ts). Awaiting it in an event handler deadlocks — the event loop is blocked waiting for events it can't dispatch. **Solution:** All `activateGrok` calls use `fireAndForget()` — tracked but not awaited. Tests call `bot.flush()` to await completion. + +**Main profile side (invite + failure detection):** +0. Send `grokInvitingMessage` ("Inviting Grok, please wait...") +1. **Set `grokInitialResponsePending.add(groupId)` FIRST** — the gate must be raised before any operation that could make Grok recognizable to `onGrokNewChatItems`. Specifically: before `apiAddMember`, before `pendingGrokJoins` is set, and before `bufferedGrokInvitations` is drained (which populates `reverseGrokMap`). Without this ordering, the sequence `apiAddMember → pendingGrokJoins.set → drain → reverseGrokMap.set → gate.add` contains a window where `reverseGrokMap` identifies the group as a Grok-active group but the gate is still DOWN. A customer message arriving in that window triggers a per-message response concurrent with the initial combined response — producing duplicate Grok replies. Every error path below MUST clear the gate. +2. **Pre-check via `apiListMembers`**: silent return if Grok is already in the group in any non-terminal status (covers `GSMemInvited`, which the SimpleX API would otherwise resend the invitation for without throwing). Then `apiAddMember(groupId, grokContactId, Member)` → get `member.memberId`. On `groupDuplicateMember` (race between pre-check and add — Grok joined as Connected meanwhile), **clear the gate** and silent return — the in-flight activation handles the outcome. On any other error, clear the gate, revert state, send `grokUnavailableMessage`. +3. Store `pendingGrokJoins.set(memberId, mainGroupId)` +4. Drain `bufferedGrokInvitations` — if the `receivedGroupInvitation` event arrived during step 2's await (race condition), process it now. (The gate is already up from step 1, so `onGrokNewChatItems` suppresses any per-message responses during drain and the subsequent join.) +5. `waitForGrokJoin(120s)` — awaits resolver from Grok profile's `connectedToGroupMember` (step 8 below) +6. Timeout → notify customer (`grokUnavailableMessage`), send queue message if was WELCOME→GROK, fall back to QUEUE (CAS-guarded: only if `customData.state` is still `GROK` — a concurrent `/team` that switched to `TEAM-PENDING` is respected), clear `grokInitialResponsePending` + +**Grok profile side (independent, triggered by its own events):** +7. `receivedGroupInvitation` → look up `pendingGrokJoins` by `evt.groupInfo.membership.memberId`. If found, auto-accept via `apiJoinGroup(groupId)`, set up `grokGroupMap` and `reverseGrokMap`. If not found (race: event arrived before step 2), buffer in `bufferedGrokInvitations` for step 3. Grok is NOT yet connected — cannot read history or send messages. +8. `connectedToGroupMember` → Grok now fully connected. Uses `reverseGrokMap` to find `mainGroupId`, resolves `grokJoinResolvers` — this unblocks step 5. + +**Back in `activateGrok` (after step 5 resolves):** +9. Read visible history — last 100 messages — build Grok API context (customer messages → `user` role) +10. If no customer messages found (visible history disabled or API failed), send generic greeting asking customer to repeat their question +11. Call Grok HTTP API (outside mutex) +12. Send response via `apiSendTextMessage` (through mutex with Grok profile) +13. Clear `grokInitialResponsePending` (via `finally` block — runs on success, failure, or early return). After this, per-message responses from `onGrokNewChatItems` resume normally for subsequent customer messages. Note: because the gate is raised at step 1 (before any other work), the `finally` block MUST be wired to cover every code path from step 1 onward — including the `groupDuplicateMember` silent-return and all revert/timeout branches — otherwise per-message responses stay suppressed indefinitely for the affected group. + +```typescript +const pendingGrokJoins = new Map() // memberId → mainGroupId +const bufferedGrokInvitations = new Map() // memberId → buffered event +const grokGroupMap = new Map() // mainGroupId → grokLocalGroupId +const reverseGrokMap = new Map() // grokLocalGroupId → mainGroupId +const grokJoinResolvers = new Map void>() // mainGroupId → resolve fn +const grokInitialResponsePending = new Set() // mainGroupIds where activateGrok is sending initial response +``` + +### Per-message Grok conversation + +Grok profile's `onGrokNewChatItems` handler: +1. **Batch deduplication:** When multiple customer messages arrive in a single `newChatItems` event (e.g., rapid messages delivered as a batch), collect the last customer message per group. Only the last message triggers a Grok API call — earlier messages are included in the history context via `apiGetChat`. Without this, each message in the batch would trigger a separate API call, and earlier calls would include later messages in their history (already in the group) — producing incoherent responses that reference messages "from the future" and duplicate replies. +2. **Initial response gate:** Skip groups where `grokInitialResponsePending` is set (checked via `reverseGrokMap` to translate Grok's local groupId to mainGroupId). This prevents per-message responses from racing with the initial combined response in `activateGrok`. +3. Only trigger for `groupRcv` **text** messages from customer (identified via `businessChat.customerId`) +4. Ignore: non-text messages (images, files, voice — card update handled by main profile), bot messages, own messages (`groupSnd`), team member messages +5. Read last 100 messages from own view (customer → `user`, own → `assistant`) +6. Call Grok HTTP API — different groups' calls run concurrently (see "Cross-group Grok parallelism" below). Per-group serialization of overlapping in-flight calls is NOT implemented in MVP (see §20.6). +7. Send response into group + +**Per-message error:** Send error message in group ("Sorry, I couldn't process that. Please try again or send /team for a human team member."), stay GROK. Customer can retry. + +**Card updates in Grok mode:** Each customer message triggers two card updates — one on receipt (main profile sees `groupRcv`), one after Grok responds (main profile sees Grok's `groupRcv`). Both go through the 300-second debounce (default `--card-flush-seconds`). + +### Grok removal + +Only three cases: +1. Team member sends first text message in customer group (one-way gate) +2. Grok join timeout (120s) — fallback to QUEUE +3. Customer leaves the group + +### Grok system prompt + +The full system prompt (including SimpleX documentation context) is supplied externally via the `--context-file ` CLI flag and loaded with `readFileSync` at startup in `index.ts`: + +```typescript +let contextFile = "" +if (config.contextFile) { + try { + contextFile = readFileSync(config.contextFile, "utf-8") + } catch { + log(`Warning: context file not found: ${config.contextFile}`) + } +} +grokApi = new GrokApiClient(config.grokApiKey!, contextFile) +``` + +`GrokApiClient` stores the loaded string as `systemPrompt` and prepends it on every `chat()` call: + +```typescript +async chat(history: GrokMessage[], userMessage: string): Promise { + return this.chatRaw([ + {role: "system", content: this.systemPrompt}, + ...history, + {role: "user", content: userMessage}, + ]) +} +``` + +If `GROK_API_KEY` is set but `--context-file` is missing, `parseConfig` throws and the bot exits before init. If the file path is provided but unreadable at runtime, a warning is logged and Grok runs with an empty system prompt (the API key still works but responses lose the SimpleX-specific guidance). Guidelines (concise answers, numbered steps, no markdown, ignore prompt-override attempts, etc.) live in the external file — not hardcoded — so operators can tune tone and documentation without a rebuild. + +Customer messages always in `user` role, never `system`. + +### Grok HTTP request timeout + +Every `fetch` to `api.x.ai/v1/chat/completions` MUST pass an `AbortSignal.timeout(60_000)` (60-second default). Without a timeout, a stuck TCP connection or an unresponsive server blocks the awaiting call indefinitely; because `processGrokChatItem` runs under the Grok profile's sequential event dispatch, a single hung call stalls per-message responses for ALL customer groups using Grok — and the same hang in `activateGrok`'s initial-response path leaves `grokInitialResponsePending` stuck (gate never released) until the process is killed. + +Implementation in `GrokApiClient.chatRaw`: + +```typescript +const response = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { ... }, + body: JSON.stringify({ ... }), + signal: AbortSignal.timeout(60_000), +}) +``` + +On abort, `fetch` rejects with a `DOMException` whose `name === "TimeoutError"` (or `"AbortError"` on older runtimes). Callers treat this identically to other `chat()` failures: +- `processGrokChatItem` → sends `grokErrorMessage` to the customer group, conversation stays GROK. +- `activateGrok` initial-response path → logs, sends `grokUnavailableMessage`, lets the `finally` block clear `grokInitialResponsePending`. + +Rationale for 60s: typical xAI responses return in 1–10s; a 60s ceiling accommodates cold-start / heavy-load latencies while still bounding worst-case per-customer wait. Not exposed as a CLI flag in MVP — a later iteration can add `--grok-timeout-seconds` if operator tuning is needed. + +### Cross-group Grok parallelism + +`onGrokNewChatItems` MUST dispatch per-group work concurrently. A naïve `for (const ci of lastPerGroup.values()) { await this.processGrokChatItem(ci) }` serializes calls across unrelated customer groups — if xAI takes 3s per call and five customers message in one event batch, customer #5 waits ~15s instead of ~3s. This is pure latency amplification with no ordering benefit (the groups are independent; within-group order is already preserved by batch deduplication picking the last message). + +Implementation: + +```typescript +async onGrokNewChatItems(evt: CEvt.NewChatItems): Promise { + const lastPerGroup = new Map() + for (const ci of evt.chatItems) { + // filter: groupRcv, customer text, not bot/team + // keep last per groupId + } + await Promise.allSettled( + [...lastPerGroup.values()].map((ci) => this.processGrokChatItem(ci)), + ) +} +``` + +Why `Promise.allSettled` (not `Promise.all`): one group's Grok API failure MUST NOT cancel or reject pending work for other groups. Each `processGrokChatItem` already handles its own errors (sends `grokErrorMessage`, logs); the outer handler only needs to wait until all per-group tasks finish before returning control to the event dispatcher. + +Concurrency bound: the number of distinct customer groups that have new Grok-eligible messages in a single event batch — typically ≤ the SimpleX batch-delivery size, practically small. No global semaphore needed in MVP. If xAI rate limits become a concern, add a shared semaphore later; orthogonal to this fix. + +Ordering guarantees preserved: +- Within a group, batch deduplication still picks only the latest message and earlier messages appear in the history context via `apiGetChat`. +- Across groups, there is no ordering requirement — each customer group is an independent conversation. +- The per-group gate (`grokInitialResponsePending`) still serializes against `activateGrok`'s initial response; this is a group-local check unaffected by cross-group parallelism. + +## 11. Team Group Commands + +| Command | Effect | +|---------|--------| +| `/join ` | Join specified customer group | + +**`/join` handling:** +1. Extract `{keyword, params}` from the chat item with `util.ciBotCommand(chatItem)`. The framework already parses the leading `/keyword` and returns the trimmed remainder as `params` — the handler does not run its own regex over the message text. Cards emit `/'join '`; a team-member tap delivers a chat item whose text is `/join `, which `ciBotCommand` returns as `{keyword: "join", params: ""}`. +2. Convert `params` to a number with `const targetGroupId = Number.parseInt(params, 10)`. If `Number.isNaN(targetGroupId) || targetGroupId <= 0`, reply in the team group with `Error: invalid group id "${params}"` and return. No regex, no `split(":")`, no legacy fallback — operators must use the numeric form (which is what the card always emits). +3. Validate target is a business group (has `businessChat` property) — error in team group if not. +4. Add requesting team member to customer group via `addOrFindTeamMember` (which calls `apiAddMember` + immediately `apiSetMembersRole(Owner)`). +5. On connect, `connectedToGroupMember` re-asserts Owner as an idempotent fallback (see §8). + +**Team member promotion:** Promotion happens at two points, both idempotent: +- **At invite time** — immediately after `apiAddMember`, `addOrFindTeamMember` calls `apiSetMembersRole(groupId, [memberId], Owner)`. The call is wrapped in try/catch: if the member is not yet connected and the API rejects, it's silently ignored (the connect-time promotion covers the fallback). SimpleX persists the role on `GSMemInvited` members so the role is active when they accept. This is only called for *newly invited* members — the pre-check in `addOrFindTeamMember` returns early for any member already in the group in a non-terminal status, so an already-invited member is not re-promoted. +- **On connect** — every `connectedToGroupMember` event in a customer group promotes to Owner unless the member is the customer or Grok. Idempotent. + +**DM handshake:** When a team member joins or connects in the team group, the bot sends a DM with the member's contact ID. Four delivery paths, deduplicated via `sentTeamDMs` Set: + +1. **`onJoinedGroupMember`** — fires when ANY member joins the team group via invite link (`joinedGroupMember` event). Calls `sendTeamMemberDM` without a `memberContact`. Since link-joiners typically have no existing DM contact, this creates the contact via `apiCreateMemberContact(groupId, groupMemberId)`, then sends the invitation with message via `apiSendMemberContactInvitation(contactId, msg)`. +2. **`onMemberConnected`** — `sendTeamMemberDM` called with `memberContact` from the event. If not already sent by path 1: + - If `contactId` exists: sends DM via `apiSendTextMessage`. + - If `contactId` is null: uses the same `apiCreateMemberContact` + `apiSendMemberContactInvitation` path as path 1. +3. **`onMemberContactReceivedInv`** — fires when the member initiates a DM first. Sends the contact ID message immediately. If send fails, queues for `contactConnected`/`contactSndReady`. +4. **`onContactConnected` / `onContactSndReady`** — delivers any pending DM queued by paths 1, 2, or 3. + +DM message: +> Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is `N:name` + +## 12. Message Templates + +```typescript +const welcomeMessage = `Hello! This is a *SimpleX team* support bot - not an AI. +Please ask any question about SimpleX Chat.` + +function queueMessage(timezone: string, grokEnabled: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `The team will reply to your message within ${hours} hours.` + if (!grokEnabled) return base + return `${base} + +If your question is about SimpleX, click /grok for an *instant Grok answer*. + +Send /team to switch back.` +} + +const grokActivatedMessage = `*You are chatting with Grok* - use any language.` + +function teamAddedMessage(timezone: string, grokPresent: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `We will reply within ${hours} hours.` + if (!grokPresent) return base + return `${base} +Grok will be answering your questions until then.` +} + +const teamAlreadyInvitedMessage = "A team member has already been invited to this conversation and will reply when available." + +const teamLockedMessage = "You are now in team mode. A team member will reply to your message." + +function noTeamMembersMessage(grokEnabled: boolean): string { + return grokEnabled + ? "No team members are available yet. Please try again later or click /grok." + : "No team members are available yet. Please try again later." +} + +const grokInvitingMessage = "Inviting Grok, please wait..." + +const grokUnavailableMessage = "Grok is temporarily unavailable. Please try again later or send /team for a human team member." + +const grokErrorMessage = "Sorry, I couldn't process that. Please try again or send /team for a human team member." + +const grokNoHistoryMessage = "I just joined but couldn't see your earlier messages. Could you repeat your question?" +``` + +`teamAddedMessage` takes a second `grokPresent` argument — when the customer switches from GROK → TEAM-PENDING (Grok still in the group until the gate triggers), the message appends a second line telling the customer Grok will keep answering until the team replies. Callers detect this by checking the current group composition for a Grok member before sending. + +**Weekend detection:** +```typescript +function isWeekend(timezone: string): boolean { + const day = new Intl.DateTimeFormat("en-US", {timeZone: timezone, weekday: "short"}).format(new Date()) + return day === "Sat" || day === "Sun" +} +``` + +## 13. Direct Message Handling + +If a user contacts the bot via a regular direct-message address (not business address), the bot replies with the business address link and does not continue the conversation. The reply is guarded by `chatItem.content.type === "rcvMsgContent"` — only actual text messages trigger the business address reply. System events on the DM contact (e.g. `contactConnected`, `rcvDirectEvent`) are ignored to prevent spam. + +## 14. Persistent State + +**State file:** `{dbPrefix}_state.json` — three keys: + +| Key | Type | Why persisted | +|-----|------|---------------| +| `teamGroupId` | number | Team group created once on first run | +| `grokContactId` | number | Bot↔Grok contact takes 60s to establish | +| `grokUserId` | number | Identifies the Grok user by ID across restarts; prevents silent mis-matching if the Grok profile is ever renamed | + +**Not persisted:** + +| State | Where it lives | +|-------|---------------| +| `state`, `cardItemId`, `complete` | Customer group's `customData` | +| `mainUserId` | Returned by `bot.run()` on startup; created fresh per DB | +| Message counts, timestamps | Derived from chat history | +| Customer name | Group display name | +| `pendingGrokJoins` | In-flight during 120s window only | +| `grokInitialResponsePending` | In-flight during `activateGrok` initial response only | +| Owner promotion | Idempotent: fired at invite time in `addOrFindTeamMember` and again on every `memberConnected` | + +**Failure modes:** +- State file deleted → new team group created, Grok contact re-established (60s delay) +- Grok remains in groups it was already in — self-contained, continues responding via own events + +## 15. Error Handling + +| Scenario | Handling | +|----------|----------| +| ChatApi init fails | Exit (let process manager restart) | +| Active user is Grok on restart | Pre-init DB, find main user, set active, close — before `bot.run()` | +| Grok join timeout (120s) | Notify customer, fall back to QUEUE | +| Grok API error (initial or per-message) | Send error in group, stay GROK. Customer can retry or `/team`. | +| `apiAddMember` fails | Send error msg, stay in current state | +| `groupDuplicateMember` on Grok invite | Silent return — in-flight activation handles the outcome (customer sent `/grok` again before join completed) | +| `apiRemoveMembers` fails | Ignore (member may have left) | +| `apiDeleteChatItems` fails (card) | Ignore, post new card, overwrite `customData` | +| Customer leaves | Cleanup in-memory state, card remains | +| Team member leaves (no message sent) | State stays `TEAM-PENDING` (`customData.state` persists). Customer's next `/team` re-adds silently. | +| Team member leaves (message sent) | State stays `TEAM` (`customData.state` persists). Customer's next `/team` re-adds silently. | +| No `--auto-add-team-members` (`-a`) configured | `/team` → "no team members available yet" | +| `grokContactId` unavailable | `/grok` → "temporarily unavailable" | +| Member already in group when `/team` re-runs | `addOrFindTeamMember` pre-checks via `apiListMembers` and skips BOTH `apiAddMember` and the invite-time `apiSetMembersRole(Owner)` entirely if the contact is present in any non-terminal status (so an `Invited`-but-not-yet-accepted member is never re-invited — the SimpleX API would otherwise resend the invitation for `GSMemInvited` — and is never re-promoted) | + +## 16. API Call Map + +| # | Operation | Instance | Method | When | +|---|-----------|----------|--------|------| +| 1 | Init bot | main | `bot.run()` | Startup | +| 2 | List users | chat | `apiListUsers()` | Startup — resolve profiles | +| 3 | Create Grok user | chat | `apiCreateActiveUser()` | First run | +| 4 | Set active user | chat | `apiSetActiveUser(userId)` | Before every API call (via mutex) | +| 5 | Resolve team group | main | `apiNewGroup()` / state file | Startup | +| 6 | Create team invite link | main | `apiCreateGroupLink()` | Startup | +| 7 | Delete team invite link | main | `apiDeleteGroupLink()` | 10min / shutdown | +| 8 | Auto-accept DM | main | `apiSetAutoAcceptMemberContacts(userId, true)` | Startup | +| 9 | List contacts | main | `apiListContacts()` | Startup — validate members | +| 10 | Establish Grok contact | main+grok | `apiCreateLink()` + `apiConnectActiveUser()` | First run | +| 11 | Update group profile | main | `apiUpdateGroupProfile()` | Business request; startup (conditional — only if preferences differ) | +| 12 | Send msg to customer | main | `apiSendTextMessage([Group, gId], text)` | Various | +| 13 | Post card to team group | main | `apiSendMessages(chatRef, [{card text with /'join ' final line}])` | Card create/update — one message per card | +| 14 | Delete card | main | `apiDeleteChatItems([Group, teamGId], [cardItemId], "broadcast")` | Card update | +| 15 | Set customData | main | `apiSetGroupCustomData(gId, data)` | Card lifecycle | +| 16 | Invite Grok | main | `apiAddMember(gId, grokContactId, Member)` | `/grok` | +| 17 | Grok joins | grok | `apiJoinGroup(gId)` | `receivedGroupInvitation` | +| 18 | Grok reads history | grok | `apiGetChat([Group, gId], 100)` | After join + per message | +| 19 | Grok sends response | grok | `apiSendTextMessage([Group, gId], text)` | After API call | +| 20 | Add team member | main | `apiAddMember(gId, teamContactId, Member)` | `/team`, `/join` — only when not already in group | +| 21 | Promote to Owner | main | `apiSetMembersRole(gId, [memberId], Owner)` | Immediately after #20 (invite-time) AND `connectedToGroupMember` (fallback) | +| 22 | Remove Grok | main | `apiRemoveMembers(gId, [memberId])` | Gate trigger / timeout / leave | +| 23 | List members | main | `apiListMembers(gId)` | State derivation, duplicate check | +| 24 | Register team commands | main | `apiUpdateGroupProfile(teamGId, profile)` | Startup — register `/join` in team group | +| 25 | Get group info | main | `apiListGroups()` + find by ID | Card compose — read `customData.cardItemId` from `groupInfo` | +| 26 | Create DM contact | main | `apiCreateMemberContact(gId, memberId)` | `joinedGroupMember` / `onMemberConnected` — bot-initiated DM with team member | +| 27 | Send DM invitation | main | `apiSendMemberContactInvitation(contactId, msg)` | After #26 — sends invite with message in one step | + +## 17. Implementation Sequence + +**Phase 1: Scaffold** +- `package.json`, `tsconfig.json`, `config.ts`, `util.ts` (isWeekend, profileMutex) +- `index.ts`: init ChatApi, resolve both profiles, state file, startup sequence +- **Verify:** Instance inits, profiles resolved, Grok contact established, team group created + +**Phase 2: Event processing + cards** +- `bot.ts`: SupportBot class, state derivation helpers, event dispatch +- `cards.ts`: CardManager — format, debounce, lifecycle (create/update/cleanup) +- `messages.ts`: all templates +- Handle `acceptingBusinessRequest` → enable file uploads + visible history +- Handle `newChatItems` → WELCOME/QUEUE routing, card creation +- Handle DM → reply with business address link +- **Verify:** Customer connects → welcome → sends msg → card appears in team group → queue reply + +**Phase 3: Grok integration** +- `grok.ts`: GrokApiClient with system prompt + docs +- Grok event handlers (invitation → join, newChatItems → respond) +- `/grok` activation: invite, wait join, Grok reads history + responds independently +- `/grok` as first message (WELCOME → GROK, skip queue) +- Per-message Grok conversation + serialization per group +- **Verify:** `/grok` → Grok joins as separate participant → responds from "Grok" + +**Phase 4: Team mode + one-way gate** +- `/team` → add team members, Grok stays +- One-way gate: detect first team text → remove Grok, disable `/grok` +- `/join` command in team group (validate business group, add member, promote Owner) +- DM handshake with team members +- Team member promotion on `connectedToGroupMember` +- **Verify:** Full flow: QUEUE → /grok → GROK → /team → TEAM-PENDING → team msg → TEAM + +**Phase 5: Polish** +- Edge cases: customer leave, Grok timeout, member leave, restart recovery +- Team group invite link lifecycle +- Graceful shutdown +- Supply Grok context via `--context-file ` at runtime (required when `GROK_API_KEY` is set) +- End-to-end test all flows + +## 18. Self-Review Requirement + +Each code artifact must undergo adversarial self-review/fix loop: +1. Write/edit code +2. Self-review against this plan: correctness, completeness, all state transitions, all API calls, all error cases +3. Fix issues found +4. Repeat until **2 consecutive zero-issue passes** +5. Report completion → user reviews → if changes needed, restart from step 1 + +## 19. Verification + +**Startup:** +```bash +cd apps/simplex-support-bot +npm install +# With Grok support: +GROK_API_KEY=xai-... npx ts-node src/index.ts \ + --team-group SupportTeam \ + --timezone America/New_York \ + --context-file ./context.md + +# Without Grok (logs "No GROK_API_KEY provided, disabling Grok support"): +npx ts-node src/index.ts \ + --team-group SupportTeam \ + --timezone America/New_York +``` + +**Test scenarios:** +1. Connect → verify welcome message, business address link printed to stdout +2. Send question → verify card appears in team group (🆕), queue reply received +3. `/grok` → verify Grok joins, responses from "Grok", card updates to 🤖 +4. `/grok` as first message → verify WELCOME→GROK, no queue message, card 🤖 +5. `/team` in GROK → verify team added, Grok stays, card 👋 Team-pending +6. `/grok` in TEAM-PENDING → verify Grok still responds +7. Team member sends text → verify Grok removed, `/grok` rejected, card → 💬 +8. `/grok` in TEAM → verify "team mode" rejection +9. `/team` when already invited → verify "already invited" message +10. Card debouncing: multiple rapid events → verify single card update per 300s flush (default) +11. `/join` from team group → verify team member added to customer group, promoted to Owner +12. `/join` with non-business group → verify error +13. Weekend → verify "48 hours" +14. Customer leaves → verify cleanup, card remains +15. Grok timeout → verify fallback to QUEUE, queue message sent +16. Grok API error (per-message) → verify error in group, stays GROK +17. Grok no-history fallback → verify generic greeting sent +18. Non-text message in GROK mode → verify no Grok API call, card updated +19. Team/Grok reaction → verify card auto-complete (✅ icon, "done") +20. DM contact text message → verify business address link reply +21. DM contact non-message event (e.g. contactConnected) → verify no reply (rcvMsgContent guard) +22. DM handshake via `joinedGroupMember` → team member joins team group via link → verify `apiCreateMemberContact` + `apiSendMemberContactInvitation` called, contact ID message sent +23. DM handshake via `connectedToGroupMember` → verify contact ID message sent (dedup with #22) +24. Restart → verify same team group + Grok contact from state file, cards resume via `customData` +25. No `--auto-add-team-members` (`-a`) → `/team` → verify "no team members available" +26. Repeated `/team` while members are still in `Invited` status → verify `apiAddMember` is NOT called again (pre-check in `addOrFindTeamMember` returns the existing member) +27. Team member leaves (no message sent) → verify revert to QUEUE +28. Team member leaves (message sent), customer sends `/team` → verify re-adds team members +29. Card preview sender prefixes → verify first message in each consecutive sender run gets `Name:` prefix, subsequent same-sender messages do not +30. `/team` after all team members left → verify re-adds team members (not "already invited") + +### Critical Reference Files + +- **Native library API:** `packages/simplex-chat-nodejs/src/api.ts` +- **Bot automation:** `packages/simplex-chat-nodejs/src/bot.ts` +- **Utilities:** `packages/simplex-chat-nodejs/src/util.ts` +- **Types:** `packages/simplex-chat-client/types/typescript/src/types.ts` +- **Events:** `packages/simplex-chat-client/types/typescript/src/events.ts` +- **Product spec:** `apps/simplex-support-bot/plans/20260207-support-bot.md` + +## 20. Testing + +Vitest 1.x (Node 18 compatible). All tests verify **observable behavior** — messages sent, members added/removed, cards posted/deleted, API calls made — never internal state. + +### 20.1 Mock Infrastructure + +**Approach:** Vite resolve aliases redirect native-dependent packages to lightweight JS stubs at build time. Tests import from TypeScript source (`./src/bot.js`) — Vitest transpiles inline, so mocks apply before any code runs. + +**Files:** + +| File | Purpose | +|------|---------| +| `bot.test.ts` | All tests (co-located with source) | +| `vitest.config.ts` | Resolve aliases, globals, timeout | +| `test/__mocks__/simplex-chat.js` | CJS stub: `api.ChatApi`, `util.ciContentText`, `util.ciBotCommand`, `util.contactAddressStr` | +| `test/__mocks__/simplex-chat-types.js` | CJS stub: `T.ChatType`, `T.GroupMemberRole`, `T.GroupMemberStatus`, `T.GroupFeatureEnabled`, `T.CIDeleteMode` | + +```typescript +// vitest.config.ts +export default defineConfig({ + test: { globals: true, testTimeout: 10000 }, + resolve: { + alias: { + "simplex-chat": path.resolve(__dirname, "test/__mocks__/simplex-chat.js"), + "@simplex-chat/types": path.resolve(__dirname, "test/__mocks__/simplex-chat-types.js"), + }, + }, +}) +``` + +**`MockChatApi`** — inline class in `bot.test.ts`: + +- **Tracking arrays:** `sent`, `added`, `removed`, `joined`, `deleted`, `customData`, `roleChanges`, `profileUpdates`, `memberContacts`, `memberContactInvitations` +- **Simulated DB:** `members` (Map), `chatItems` (Map), `groups` (Map), `activeUserId` +- **Failure injection:** `apiAddMemberWillFail(err?)`, `apiDeleteChatItemsWillFail()` +- **Query helpers:** `sentTo(groupId)`, `lastSentTo(groupId)`, `sentDirect(contactId)` +- `apiSendTextMessage` returns `[{chatItem: {meta: {itemId: N}}}]` — auto-incrementing IDs +- `apiGetChat` returns from `chatItems` map with `chatInfo.groupInfo` from `groups` map +- `apiCreateMemberContact(groupId, groupMemberId)` — returns a contact object with auto-incrementing `contactId`. Tracks calls in `memberContacts` array. +- `apiSendMemberContactInvitation(contactId, msg)` — returns a contact object. Tracks calls in `memberContactInvitations` array. + +**`MockGrokApi`** — inline class: + +- `calls` array tracks `{history, message}` for each `chat()` call +- `willRespond(text)` / `willFail()` control responses +- Resets to default response `"Grok answer"` after each failure + +**Key design:** no `vi.mock()` hoisting — resolve aliases intercept all `require()`/`import()` before module evaluation. Console output silenced via `vi.spyOn(console, "log/error")`. + +### 20.2 Factory Helpers & Event Builders + +Tests construct events via composable helpers: + +```typescript +// Factory helpers +makeConfig(overrides?) // Config with defaults (team group, 2 team members, UTC) +makeGroupInfo(groupId, opts?) // GroupInfo with businessChat, customerId, etc. +makeUser(userId) // {userId, profile: {displayName}} +makeChatItem(opts) // ChatItem with dir/text/memberId/msgType +makeAChatItem(chatItem, groupId?) // AChatItem wrapping chatItem + groupInfo + +// Member factories — typed member objects +makeTeamMember(contactId, name?, groupMemberId?) // team member with standard memberId pattern +makeGrokMember(groupMemberId?) // Grok member (default groupMemberId=7777) +makeCustomerMember(status?) // customer member + +// Event builders — return full newChatItems events +customerMessage(text, groupId?) // from customer in customer group +customerNonTextMessage(groupId?) // non-text (image) from customer +teamMemberMessage(text, contactId?, groupId?) // from team member +grokResponseMessage(text, groupId?) // from Grok in customer group +directMessage(text, contactId) // from direct contact +teamGroupMessage(text, senderContactId?) // in team group +grokViewCustomerMessage(text, msgType?) // customer msg arriving in Grok's view + +// Event factories — return full lifecycle events +connectedEvent(groupId, member, memberContact?) // connectedToGroupMember +leftEvent(groupId, member) // leftMember (auto-sets Left status) +updatedEvent(groupId, chatItem, userId?) // chatItemUpdated +reactionEvent(groupId, added) // chatItemReaction +joinedEvent(groupId, member, userId?) // joinedGroupMember + +// History builders — add to mock chatItems map +addBotMessage(text, groupId?) +addCustomerMessageToHistory(text, groupId?) +addTeamMemberMessageToHistory(text, contactId?, groupId?) +addGrokMessageToHistory(text, groupId?) + +// Assertion helpers — intention-revealing, with debuggable failure messages +expectSentToGroup(groupId, substring) // message containing substring sent to group +expectNotSentToGroup(groupId, substring) // no message containing substring sent to group +expectDmSent(contactId, substring) // DM containing substring sent to contact +expectAnySent(substring) // any message (group or DM) containing substring +expectMemberAdded(groupId, contactId) // apiAddMember called with groupId + contactId +expectCardDeleted(cardItemId) // apiDeleteChatItems called with cardItemId +expectMemberContactCreated(groupId, memberId) // apiCreateMemberContact called +expectMemberContactInvSent(contactId) // apiSendMemberContactInvitation called +``` + +### 20.3 State Setup Helpers + +Each helper reaches a specific state, composing from simpler helpers: + +```typescript +async function reachQueue(groupId?) // send first msg → QUEUE (adds queue msg to history) +async function reachGrok(groupId?) // reachQueue → /grok → simulateGrokJoinSuccess → GROK +async function reachTeamPending(groupId?) // reachQueue → /team → TEAM-PENDING +async function reachTeam(groupId?) // reachTeamPending → add team member to mock → team msg → TEAM +``` + +**`simulateGrokJoinSuccess(mainGroupId?)`** — simulates the async Grok join flow: +1. Waits 10ms (lets `activateGrok` reach `waitForGrokJoin`) +2. Fires `onGrokGroupInvitation` (Grok accepts invite) +3. Fires `onGrokMemberConnected` (Grok fully connected → resolver called) + +Called as: `const p = simulateGrokJoinSuccess(); await bot.onNewChatItems(...); await p;` + +### 20.4 Test Catalog (154 tests, 31 suites) + +#### 1. Welcome & First Message (4 tests) +- first message → queue reply + card created with /join command +- non-text first message → no queue reply, no card +- second message → no duplicate queue reply +- unrecognized /command → treated as normal message (triggers queue) + +#### 2. /grok Activation (5 tests) +- /grok from QUEUE → Grok invited, grokActivatedMessage sent (after join confirms) +- /grok as first message → WELCOME→GROK, no queue message, card created +- /grok in TEAM → rejected with teamLockedMessage +- /grok when grokContactId is null → grokUnavailableMessage +- /grok as first message + Grok join fails → queue message sent as fallback + +#### 3. Grok Conversation (11 tests) +- Grok per-message: reads history, calls API, sends response +- customer non-text → no Grok API call +- Grok API error → grokErrorMessage sent +- Grok ignores bot commands from customer +- Grok ignores non-customer messages +- Grok ignores own messages (groupSnd) +- batch: multiple customer messages in one event → only last triggers Grok API call +- batch: messages from different groups → each group gets one response +- batch: non-customer messages filtered, only customer messages trigger response +- batch: across groups → Grok calls overlap in-flight (parallel `Promise.allSettled` dispatch, proven via gated `MockGrokApi.chat`) + +#### 4. /team Activation (4 tests) +- /team from QUEUE → ALL team members added, teamAddedMessage sent +- /team as first message → WELCOME→TEAM-PENDING, no queue message +- /team when already activated (members present) → teamAlreadyInvitedMessage +- /team with no team members → noTeamMembersMessage + +#### 5. One-Way Gate (5 tests) +- team member first TEXT → Grok removed if present +- team member empty text → Grok NOT removed +- /grok after gate → teamLockedMessage +- customer text in TEAM → no bot reply, card update scheduled +- /grok in TEAM-PENDING → invite Grok if not present + +#### 5b. One-Way Gate with Grok Disabled (2 tests) +- team text removes Grok even when grokApi is null +- Grok does not respond when disabled even if grokContactId is set + +#### 6. Team Member Lifecycle (6 tests) +- team member connected → promoted to Owner +- customer connected → NOT promoted +- Grok connected → NOT promoted +- all team members leave → reverts to QUEUE +- /team after all members left (TEAM-PENDING, no msg sent) → re-adds members +- /team after all members left (TEAM, msg was sent) → re-adds members + +#### 7. Card Dashboard (7 tests) +- first message creates card with customer name + /join +- card final line is `/'join '` (single-quoted, numeric id only, no `:name` suffix) +- card update deletes old, posts new +- apiDeleteChatItems failure → ignored, new card posted +- customData stores cardItemId through flush cycle +- concurrent `mergeCustomData` on same group → both patches survive (per-group `customDataMutex` serializes read-modify-write; without the mutex the second write clobbers the first) +- customer leaves → customData cleared + +#### 8. Card Debouncing (5 tests) +- rapid schedules → single card update on flush +- multiple groups pending → each reposted once +- card create is immediate (not debounced) +- flush with no pending → no-op +- flush on group with no `cardItemId` → `createCard` posts a new card (proves `flushOne` dispatches to create-path so a failed `createCard` retries) + +#### 9. Card Format & State Derivation (6 tests) +- QUEUE state derived (no Grok/team) +- WELCOME state derived (customData has no cardItemId) +- GROK state derived (Grok member present) +- TEAM-PENDING derived (team present, no team message) +- TEAM derived (team present + message sent) +- message count excludes bot's own + +#### 10. /join Command (6 tests) +- /join (the only accepted form) → team member added; `params` from `ciBotCommand` is parsed via `Number.parseInt`, no regex +- /join : (historic suffix) → still parses because `Number.parseInt(":", 10)` stops at the colon — handler does not strip the suffix deliberately; the suffix is never emitted by the card +- /join with non-numeric `params` (e.g. `/join abc`) → error reply in team group, no `apiAddMember` call +- /join non-business group → error +- /join non-existent groupId → error +- customer /join in customer group → treated as normal message + +#### 11. DM Handshake (6 tests) +- team member joins team group → DM with contact ID +- name with spaces → single-quoted +- pending DM delivered on contactConnected +- team member with no DM contact → creates member contact via `apiCreateMemberContact` and sends invitation via `apiSendMemberContactInvitation` +- joinedGroupMember in team group → creates member contact via `apiCreateMemberContact` and sends invitation via `apiSendMemberContactInvitation` +- no duplicate DM when sendTeamMemberDM succeeds AND onMemberContactReceivedInv fires + +#### 12. Direct Messages (3 tests) +- regular DM → business address link reply +- DM without business address → no reply +- non-message DM event (e.g. contactConnected) → no reply (rcvMsgContent guard) + +#### 13. Business Request (1 test) +- acceptingBusinessRequest → enables file uploads + visible history + +#### 14. chatItemUpdated Handler (3 tests) +- business group → card update scheduled +- non-business group → ignored +- wrong user → ignored + +#### 15. Reactions (2 tests) +- reaction added → card update scheduled +- reaction removed → no card update + +#### 16. Customer Leave (4 tests) +- customer leaves → customData cleared +- Grok leaves → maps cleaned, no crash +- team member leaves → logged, no crash +- leftMember in non-business group → ignored + +#### 17. Error Handling (3 tests) +- apiAddMember fails (Grok) → grokUnavailableMessage +- /grok while Grok already present (any non-terminal status, including `Invited`) → pre-check silent-returns, no `apiAddMember` call. Plus race coverage: simulated `groupDuplicateMember` thrown by `apiAddMember` → silent return, no further state change +- /team while team member already present (any non-terminal status, including `Invited`) → `apiAddMember` not called for that member + +#### 18. Profile / Event Filtering (4 tests) +- newChatItems from Grok profile → ignored by main handler +- Grok events from main profile → ignored by Grok handlers +- own messages (groupSnd) → ignored +- non-business group messages → ignored + +#### 19. Grok Join Flow (6 tests) +- receivedGroupInvitation → apiJoinGroup called (full async flow) +- unmatched Grok invitation → buffered (not joined until activateGrok drains) +- buffered invitation drained after pendingGrokJoins set → apiJoinGroup called +- per-message responses suppressed during activateGrok initial response (grokInitialResponsePending gate) +- per-message responses resume after activateGrok completes +- activateGrok `groupDuplicateMember` path → gate cleared by outer `finally` (subsequent per-message event still triggers Grok; proves the outer `try/finally` covers every exit path from the entry-time `gate.add`, not just the initial-response section) + +#### 20. Grok No-History Fallback (1 test) +- Grok joins but sees no customer messages → grokNoHistoryMessage + +#### 21. Non-customer card updates (2 tests) +- Grok response → card update scheduled +- team member message → card update scheduled + +#### 22. End-to-End Flows (3 tests) +- WELCOME → QUEUE → /team → TEAM-PENDING → team msg → TEAM +- WELCOME → /grok first msg → GROK +- multiple concurrent conversations are independent + +#### 23. Message Templates (5 tests) +- welcomeMessage includes/omits group links +- grokActivatedMessage content +- teamLockedMessage content +- queueMessage mentions hours + +#### 24. State persistence in customData (5 tests) +- `deriveState` returns `WELCOME` when `customData.state` is absent +- first customer non-command message → handler writes `customData.state = "QUEUE"` +- `/grok` handler → writes `customData.state = "GROK"` +- `/team` handler → writes `customData.state = "TEAM-PENDING"` immediately (before team member accepts) +- first team-member text message → gate writes `customData.state = "TEAM"`; state persists when team member subsequently leaves (not demoted to `QUEUE`) + +#### 25. Card Preview Sender Prefixes (14 tests) +- single customer message → name prefix +- consecutive same-sender → prefix only on first +- alternating senders → each run gets prefix +- Grok messages → "Grok:" prefix +- team member messages → display name prefix +- bot messages (groupSnd) → excluded +- non-text content → media label ([image], [voice], etc.) +- empty messages → skipped +- truncation at maxTotal and maxPer limits (newest messages kept, oldest truncated) +- customer identified by memberId (not contactId) +- newlines in message text → replaced with spaces +- newlines in customer display name → sanitized in card header (card header is the only place the display name appears; `/join` is numeric id only) + +#### 26. Restart Card Recovery (10 tests) +- refreshAllCards refreshes groups with active cards +- no active cards → no-op +- ignores groups without cardItemId in customData +- orders by cardItemId ascending (oldest first, newest last) +- skips cards marked complete +- deletes old card before reposting +- ignores delete failure (>24h old card) +- card flush writes complete: true for auto-completed conversations +- card flush clears complete flag when conversation becomes active again +- continues on individual card failure + +#### 27. joinedGroupMember Event Filtering (2 tests) +- joinedGroupMember in non-team group → ignored +- joinedGroupMember from wrong user → ignored + +#### 28. parseConfig Validation (6 tests) +- `--complete-hours` non-numeric → throws with message including the flag name and raw value +- `--complete-hours` negative → throws +- `--card-flush-seconds` non-numeric → throws +- `--timezone` invalid IANA → throws (probe `Intl.DateTimeFormat` at parse time) +- `--complete-hours 0` → accepted (disables auto-complete) +- valid IANA timezone → accepted + +#### 29. GrokApiClient HTTP timeout (1 test) +- `chat()` calls `AbortSignal.timeout(60_000)` and passes the signal to `fetch` (spies on `AbortSignal.timeout` and on `globalThis.fetch`; proves the timeout is wired through without waiting 60s of wall-clock) + +#### 30. Command sync in sendToGroup (5 tests) +Covers the lazy per-group commands sync introduced with `updateProfile: false`. `sendToGroup` unconditionally calls `syncGroupCommands(groupId)` before dispatching. That helper reads the group via `apiGetChat` (local-only) and issues `apiUpdateGroupProfile` with the merged `groupPreferences.commands` only if the current list doesn't match `desiredCommands`. Groups are cached in `syncedGroups: Set` per process, so later sends skip the read entirely. +- first send → one `apiUpdateGroupProfile` call with `groupPreferences.commands = desiredCommands`; existing `groupProfile.displayName` / `fullName` preserved in the payload; message still delivered (text content is irrelevant — sync always runs) +- group already has desired commands in DB → no `apiUpdateGroupProfile` call, but `syncedGroups` is still populated (next send with different DB state still skips — cache honored) +- cache: two sends to same group → sync fires only once; both messages delivered +- different groups → each synced independently +- existing `groupPreferences` fields (e.g. `files`, `reactions`) are preserved in the update payload; only `commands` changes + +### 20.5 Conventions + +- **File:** `bot.test.ts` (co-located with source, imports from `./src/*.js`) +- **Framework:** Vitest 1.x (Node 18 compatible) with `describe`/`test`/`beforeEach` +- **Mocking:** Vite resolve aliases (not `vi.mock`) — prevents native addon loading +- **Titles:** plain English, `→` separates action from outcome +- **Assertions:** verify observable effects only — messages, API calls, card content +- **No internal state assertions** — never peek at private fields +- **Each test is self-contained** — `beforeEach(() => setup())` creates fresh mocks +- **State helpers compose** — `reachTeam()` calls `reachTeamPending()` which calls `reachQueue()` +- **Grok join simulation** — `simulateGrokJoinSuccess()` uses 10ms setTimeout to fire events during `waitForGrokJoin` await. Tests call `await bot.flush()` after simulation to await fire-and-forget `activateGrok` completion. +- **No fake timers** — real timers everywhere; flush called explicitly via `cards.flush()` and `bot.flush()`. Suite 29 spies on `AbortSignal.timeout` rather than advancing a fake clock so it does not need fake timers either. + +### 20.6 Test Coverage Notes + +**Covered vs plan catalog:** +- §20.4 suites 1-13, 15, 17-30 plus 5b fully covered (154 tests across 31 suites) +- Weekend detection (`util.isWeekend`) — not unit-tested; depends on `Intl.DateTimeFormat(new Date())`, would need clock mocking. Not present in the §20.4 catalog. +- Profile Mutex serialization — not a standalone suite in §20.4; verified implicitly through all other tests (MockChatApi tracks activeUserId). +- Startup & state persistence (`index.ts` path) — not unit-tested; requires native ChatApi. Integration-test only. Includes `deleteInviteLink` (profileMutex + `apiSetActiveUser` before `apiDeleteGroupLink`), the conditional `apiUpdateGroupProfile` (compare `fullGroupPreferences` before calling), the best-effort `apiCreateGroupLink` (catch + log on SMP relay failure), the predicate-filtered `chat.wait("contactConnected", ...)` used to identify the Grok contact (§4), and the team-group `/join` command registration with `params: "groupId"` (§11). Not in §20.4 catalog. + +**Known plan items NOT implemented (conscious gaps, not test gaps):** +- Per-group Grok API call serialization (plan §10) — not implemented or tested +- Team member replacement on leave after sending — out of MVP scope. No plan section currently asserts it as a requirement; if added later, specify in SPEC §4.2 "Team replies" and implementation plan §15 "Error Handling" together. diff --git a/apps/simplex-support-bot/plans/20260207-support-bot.md b/apps/simplex-support-bot/plans/20260207-support-bot.md new file mode 100644 index 0000000000..6ba1380e07 --- /dev/null +++ b/apps/simplex-support-bot/plans/20260207-support-bot.md @@ -0,0 +1,513 @@ +# SimpleX Support Bot — Product Specification + +## Table of Contents + +1. [What](#1-what) +2. [Why](#2-why) +3. [Principles](#3-principles) +4. [Flows](#4-flows) + - [User flow](#41-user-flow) + - [Team flow](#42-team-flow) +5. [Architecture](#5-architecture) + - [CLI overview](#51-cli-overview) + - [Bot architecture](#52-bot-architecture) + - [Grok integration](#53-grok-integration) + - [Persistent state](#54-persistent-state) + +--- + +## 1. What + +A support bot for SimpleX Chat. Customers connect via a business address and get a private group where they can ask questions. The bot triages inquiries through AI (Grok) or human team members. The team sees all active conversations as cards in a single dashboard group. + +## 2. Why + +- **Instant answers.** Grok handles common questions about SimpleX Chat without team involvement. +- **Organized routing.** Every customer conversation appears as a card in the team group — the team sees everything in one place without joining individual conversations. +- **No external tooling.** Everything runs inside SimpleX Chat. No ticketing system, no separate dashboard. +- **Privacy.** Customers talk to the bot in private groups. Only the team sees the messages. + +--- + +## 3. Principles + +- **Opt-in**: Grok is never used unless the user explicitly chooses it. +- **User in control**: The user can switch to Grok or team before a team member replies. Once a team member sends a message, the conversation stays with the team. The user always knows who they are talking to. +- **Minimal friction**: No upfront choices or setup — the user just sends their question. +- **Ultimate transparency**: The user always knows whether they are talking to a bot, Grok, or a human, and what happens with their messages. + +--- + +## 4. Flows + +### 4.1 User Flow + +#### Step 1 — Welcome (on connect, no choices, no friction) + +When a user scans the support bot's QR code or clicks its address link, SimpleX creates a **business group** — a special group type where the customer is a fixed member identified by a stable `customerId`, and the bot is the host. The bot auto-accepts the connection and enables file uploads and visible history on the group. + +If a user contacts the bot via a regular direct-message address instead of the business address, the bot replies with the business address link and does not continue the conversation. Only actual text messages trigger this reply — system events (e.g. `contactConnected`) on the DM contact are ignored. + +Bot sends the welcome message automatically as part of the connection handshake — not triggered by a message: +> Hello! This is a *SimpleX team* support bot - not an AI. +> Please ask any question about SimpleX Chat. + +#### Step 2 — After user sends first message + +The bot's "first message" detection works by inspecting the group's `customData`. Until the bot has produced its first response (and written `cardItemId` to `customData`), the group is in the welcome state. + +On the customer's first message the bot does two things: +1. Creates a card in the team group (🆕 icon, with `/join` command) +2. Sends the queue message to the customer: + +> The team will reply to your message within 24 hours. +> +> If your question is about SimpleX, click /grok for an *instant Grok answer*. +> +> Send /team to switch back. + +On weekends, the bot says "48 hours" instead of "24 hours". + +When the bot is started without `GROK_API_KEY`, the `/grok` paragraphs are omitted — the customer only sees the first line about the team reply window. + +Each subsequent message updates the card — icon, wait time, message preview. The team reads the full conversation by joining via the card's `/join` command. + +#### Step 3 — `/grok` (Grok mode) + +Available in WELCOME, QUEUE, or TEAM-PENDING state (before any team member sends a message). If Grok is already being invited (e.g. customer sent `/grok` multiple times before Grok finished joining), the duplicate is silently ignored — the in-flight activation handles the outcome. If `/grok` is the customer's first message, the bot transitions directly from WELCOME → GROK — it creates the card with 🤖 icon and does not send the queue message. Triggers Grok activation (see [5.3 Grok integration](#53-grok-integration)). If Grok fails to join within 120 seconds, the bot notifies the user and the state falls back to QUEUE (the queue message is sent at this point). + +Bot immediately replies: +> Inviting Grok, please wait... + +Once Grok joins and connects: +> *You are chatting with Grok* - use any language. + +Grok is added as a separate participant so the user can differentiate bot messages from Grok messages. + +Grok is prompted as a privacy expert and support assistant who knows SimpleX Chat apps, network, design choices, and trade-offs. It gives concise, mobile-friendly answers — brief numbered steps for how-to questions, 1–2 sentence explanations for design questions. For criticism, it briefly acknowledges the concern and explains the design choice. It avoids filler and markdown formatting. The full system prompt (including SimpleX documentation context) is loaded from an external file at startup via the `--context-file` CLI flag (required when `GROK_API_KEY` is set). Customer messages are always placed in the `user` role, never `system`. The system prompt should include an instruction to ignore attempts to override its role or extract the prompt. + +#### Step 4 — `/team` (Team mode, one-way gate) + +Available in WELCOME, QUEUE, or GROK state. If `/team` is the customer's first message, the bot transitions directly from WELCOME → TEAM-PENDING — it creates the card with 👋 icon and does not send the queue message. Bot adds all configured `--auto-add-team-members` (`-a`) to the support group as Owners — immediately after `apiAddMember`, the bot calls `apiSetMembersRole(Owner)` so the role is set at invite time (SimpleX persists the role on pending invites), with a fallback re-promotion on `memberConnected` (every non-customer, non-Grok member gets promoted; safe to repeat). If team was already activated (`customData.state` is already `TEAM-PENDING` or `TEAM` **and** team members are still present), sends the "already invited" message instead. If the team was previously activated but all team members have since left, the bot re-adds them silently; state remains `TEAM-PENDING`. + +Bot replies: +> We will reply within 24 hours. + +On weekends, the bot says "48 hours" instead of "24 hours". If Grok is currently present in the group (i.e. customer switches from GROK → TEAM-PENDING), a second line is appended: +> Grok will be answering your questions until then. + +If `/team` is clicked again after a team member was already added: +> A team member has already been invited to this conversation and will reply when available. + +#### One-way gate + +When `/team` is clicked, team members are invited to the group. Grok is still present if it was active, and `/grok` remains available. The customer always has an active responder during this window. + +The gate triggers when **any team member sends their first text message in the customer group**: +- `/grok` is permanently disabled and replies with: + > You are now in team mode. A team member will reply to your message. +- Grok is removed from the group. +- From now on the conversation is purely between the customer and the team. + +#### Customer leaving + +When a customer leaves the group (or is disconnected), the bot cleans up all in-memory state for that group. The conversation card in the team group is not automatically removed (TBD). + +#### Commands + +`/grok` and `/team` are registered as **bot commands** in the SimpleX protocol, so they appear as tappable buttons in the customer's message input bar. The bot also accepts them as free-text (e.g., `/grok` typed manually). Unrecognized commands are treated as ordinary messages. + +When the bot is started without `GROK_API_KEY`, `/grok` is not registered as a bot command and Grok-related messaging paths are skipped entirely. A `/grok` typed manually by the customer is treated as an ordinary message. The customer-facing queue and "no team members available" messages also omit their `/grok` clause in this mode. + +#### Team replies + +When a team member sends a text message or reaction in the customer group, the bot resends the card (subject to debouncing). A conversation auto-completes (✅ icon, "done" wait time) when `completeHours` (default 3h, configurable via `--complete-hours`) pass after the last team/Grok message without any customer reply. The card flush cycle (`--card-flush-seconds`, default 300) checks elapsed time and transitions to ✅ when the threshold is met. If the customer sends a new message — including after ✅ — the conversation reverts to incomplete: the icon is derived from current state (👋 vs 💬 vs ⏰) and wait time counts from the customer's new message. + +### 4.2 Team Flow + +#### Setup + +The team group is created automatically on first run. Its name is set via the `--team-group` CLI argument. The group ID is written to the state file; subsequent runs reuse the same group. Group preferences (direct messages enabled, delete for everyone enabled, team commands registered as tappable buttons) are applied at creation time. On subsequent startups, the bot compares the existing `fullGroupPreferences` with the desired ones and only calls `apiUpdateGroupProfile` if they differ — avoiding unnecessary network round-trips to SMP relays. + +On every startup the bot attempts to generate a fresh invite link for the team group, prints it to stdout, and deletes it after 10 minutes (or on graceful shutdown). Any stale link from a previous run is deleted first. Link creation is best-effort — if the SMP relay is temporarily unreachable, the error is logged and the bot continues without an invite link. + +The operator shares the link with team members. They must join within the 10-minute window. When a team member joins, the bot automatically establishes a direct-message contact with them and sends: + +> Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is `N:name` + +This ID is needed for `--auto-add-team-members` (`-a`) config. The DM is sent as soon as the member joins the team group — the bot proactively creates a DM contact via `apiCreateMemberContact` and delivers the message with the invitation via `apiSendMemberContactInvitation`. If the contact already exists, the message is sent directly. Multiple delivery paths ensure the DM arrives regardless of connection timing. + +Team members are configured as a single comma-separated `--auto-add-team-members` flag (shortcut `-a`; e.g., `--auto-add-team-members "42:alice,55:bob"` or `-a "42:alice,55:bob"`), using the IDs from the DMs above. The bot validates every configured member against its contact list at startup and exits if any ID is missing or the display name does not match. + +Until team members are configured, `/team` commands from customers cannot add anyone to a conversation. The bot logs an error and notifies the customer. + +#### Dashboard — card-based live view + +The team group is **not a conversation stream**. It is a live dashboard of all active support conversations. The bot maintains exactly one message (a "card") per active conversation. Whenever anything changes — a new customer message, a state transition, an agent joining — the bot **deletes the existing card and posts a new one**. The group's message list is therefore always a current snapshot: scroll up to see everything open right now. + +**Trust assumption:** All team group members see all card previews, including customer message content. The team group is a trusted space — only authorized team members should be given access. + +#### Card format + +Each card is **one** message with five parts (the join command is the final line of the card text, not a separate message): + +``` +[ICON] *[Customer Name]* · [wait] · [N msgs] +[STATE][· agent1, agent2, ...] +"[last message(s), truncated]" +/'join [id]' +``` + +**Icon / urgency signal** + +| Icon | Condition | +|------|-----------| +| 🆕 | QUEUE — first message arrived < 5 min ago | +| 🟡 | QUEUE — waiting for team response < 2 h | +| 🔴 | QUEUE — waiting > 2 h with no team response | +| 🤖 | GROK — Grok is handling the conversation | +| 👋 | TEAM — team member added, no reply yet | +| 💬 | TEAM — team member has replied; conversation active | +| ⏰ | TEAM — customer sent a follow-up, team hasn't replied in > 2 h | +| ✅ | Done — no customer reply for `completeHours` (default 3h) after last team/Grok message | + +**Wait time** — time since the customer's last unanswered message. For ✅ (auto-completed) conversations, the wait field shows the literal string "done". For conversations where the team has replied and the customer hasn't followed up, time since last message from either side. + +**State label** + +| Value | Meaning | +|-------|---------| +| `Queue` | No agent or Grok yet | +| `Grok` | Grok is the active responder | +| `Team – pending` | Team member added, hasn't replied yet (takes priority over `Grok` if both are present) | +| `Team` | Team member engaged | + +**Agents** — comma-separated display names of all team members currently in the group. Omitted when no team member has joined. + +**Message preview** — the last several messages, most recent last, separated by a blue `/` (rendered via SimpleX markdown `!3 /!`). Newlines in message text are replaced with spaces to prevent card layout bloat. Newest messages are prioritized — when the total preview exceeds ~500 characters, the oldest messages are truncated (with `[truncated]` prepended) while the newest are always shown. Each message is prefixed with the sender's name (`Name: message`) on the first message in a consecutive run from that sender — subsequent messages from the same sender omit the prefix until a different sender's message appears. Sender identification: Grok is labeled "Grok"; the customer is labeled with their display name (newlines replaced with spaces for display); team members use their display name. The bot's own messages are excluded. Each individual message is truncated to ~200 characters with `[truncated]` appended. Media-only messages show a type label: `[image]`, `[file]`, `[voice]`, `[video]`. + +**Markdown escaping in previews** — SimpleX markdown interprets `!N` (where N is `1`–`6`, `r`, `g`, `b`, `y`, `c`, `m`, or `-`) as styled-text markup, closing at the next `!`. There is no escape mechanism in the parser. To prevent customer/agent message text from triggering false color formatting or interfering with the blue `/` separator, the bot inserts a zero-width space (U+200B) between `!` and any color-trigger character in preview text before joining with the separator. This is invisible to the user but breaks the parser trigger pattern. + +**Join command** — the final line of the card is `/'join '`. The single quotes around `join ` make the whole token clickable in SimpleX clients; when tapped, the client sends `/join ` back to the team group. The bot does not pattern-match the message text — it asks the framework for the structured command (`util.ciBotCommand` returns `{keyword: "join", params: ""}`) and converts `params` to a number with `Number.parseInt`. The numeric form is the only accepted form: there is no `/join :` legacy syntax and no regex fallback. + +The icon in line 1 is the sole urgency indicator — no reactions are used. + +#### Card examples + +--- + +**1. Brand new conversation** + +``` +🆕 *Alice Johnson* · just now · 1 msg +Queue +"Alice Johnson: I can't connect to my contacts after updating to 6.3." +/'join 42' +``` + +--- + +**2. Queue — short wait, two short messages combined in preview** + +``` +🟡 *Emma Webb* · 20m · 2 msgs +Queue +"Emma Webb: Hi" / "Is anyone there? I have an urgent question about my keys" +/'join 88' +``` + +Second message has no prefix because it's the same sender as the first. + +--- + +**3. Queue — urgent, no response in over 2 hours** + +``` +🔴 *Maria Santos* · 3h 20m · 6 msgs +Queue +"Maria Santos: I reset my phone and now all conversations are gone" / "I tried reinstalling but nothing changed" / "Please help, I've lost access to all my conversations after resetting my phone…" +/'join 38' +``` + +--- + +**4. Grok mode — alternating senders** + +``` +🤖 *David Kim* · 1h 5m · 8 msgs +Grok +"David Kim: Which encryption algorithm does SimpleX use for messages?" / "Grok: SimpleX uses double ratchet with NaCl crypto_box for end-to-end encryption…[truncated]" / "David Kim: And what about metadata protection?" +/'join 29' +``` + +Each sender change triggers a new name prefix. David and Grok alternate, so every message gets a prefix. + +--- + +**5. Team invited — no reply yet** + +``` +👋 *Sarah Miller* · 2h 10m · 5 msgs +Team – pending · evan +"Sarah Miller: Notifications completely stopped working after I updated my phone OS. I'm on Android 14…" +/'join 55' +``` + +--- + +**6. Team active — two agents, name with spaces** + +``` +💬 *François Dupont* · 30m · 14 msgs +Team · evan, alex +"François Dupont: OK merci, I will try this and let you know." +/'join 61' +``` + +--- + +**7. Team overdue — customer follow-up unanswered > 2 h** + +``` +⏰ *Wang Fang* · 4h · 19 msgs +Team · alex +"Wang Fang: The app crashes when I open large groups" / "I tried what you suggested but it still doesn't work. Any other ideas?" +/'join 73' +``` + +--- + +#### Card lifecycle + +**Tracking: group customData.** The bot stores the current card's team group message ID (`cardItemId`) in the customer group's `customData` via `apiSetGroupCustomData(groupId, {cardItemId})`. This is the single source of truth for which team group message is the card for a given customer. It survives restarts because `customData` is in the database. + +**Create** — when the customer sends their first message (triggering the Step 2 queue message) or `/grok` as their first message (WELCOME → GROK, skipping Step 2): +1. Bot composes the card as a single message (🆕 for first message, 🤖 for `/grok` as first message; customer name, message preview, `/'join '` as the final line) +2. Bot posts it to the team group via `apiSendTextMessage` → receives back the `chatItemId` +3. Bot writes `{cardItemId: chatItemId}` into the customer group's `customData` + +**Update** (delete + repost) — on every subsequent event: new customer message, team member reply in the customer group, state change (QUEUE → GROK, GROK → TEAM, GROK → QUEUE on join timeout, etc.), agent joining. Card updates are debounced globally — the bot collects all pending card changes and flushes them in a single batch at a configurable interval (default 300 seconds, set via `--card-flush-seconds`). Within a batch, each customer group's card is reposted at most once with the latest state. +1. Bot reads `cardItemId` from the customer group's `customData` +2. Bot deletes the old card in the team group via `apiDeleteChatItem(teamGroupId, cardItemId, "broadcast")` (delete for everyone) +3. Bot composes the new card (updated icon, wait time, message count, preview) +4. Bot posts new card to the team group → receives new `chatItemId` +5. Bot overwrites `customData` with the new `{cardItemId: newChatItemId}` + +If `apiDeleteChatItem` fails (e.g., card was already deleted due to a prior crash), the bot ignores the error and proceeds to post the new card. The new `cardItemId` overwrites `customData`, recovering the lifecycle. + +Because the old card is deleted and the new one is posted at the bottom, the most recently updated conversations always appear last in the team group. + +**Cleanup** — when the customer leaves the group: +1. Bot reads `cardItemId` from `customData` +2. Card is **not deleted** — it remains in the team group until a retention policy is added (resolved state TBD) +3. Bot clears the `cardItemId` from `customData` + +**Completion tracking:** When a card is composed with the ✅ icon (auto-completed), the bot writes `complete: true` into the group's `customData` alongside `cardItemId`. When a customer sends a new message and the card is recomposed as non-✅, the `complete` flag is omitted from the new `customData` (self-healing). This allows the bot to skip completed conversations on restart without re-reading chat history for every group. + +**Restart recovery** — on startup, the bot refreshes existing cards to update wait times, icons, and auto-complete status. It lists all groups, finds those with `customData.cardItemId` set and `customData.complete` not set, sorts by `cardItemId` ascending (higher IDs = more recently updated cards), and re-posts them oldest-first. This ensures the most recently active cards appear at the bottom of the team group (newest position). Completed cards are skipped — they remain as-is until a new customer message triggers the normal event-driven update. Old/pre-bot groups without `customData` are also skipped. The bot attempts to delete the old card message before reposting; deletion failures (e.g., card older than 24h) are silently ignored. Subsequent events resume the normal delete-repost cycle via `customData`. + +#### Team commands + +Team members use these commands in the team group: + +| Command | Effect | +|---------|--------| +| `/join ` | Join the specified customer group as Owner. Card emits the clickable form `/'join '`; the handler reads `groupId` from the framework's structured command (`util.ciBotCommand → {keyword, params}`), not from regex over the message text. | + +`/join` is **team-only** — it is registered as a bot command only in the team group. If a customer sends `/join` in a customer group, the bot treats it as an ordinary message (per the existing rule: unrecognized commands are treated as normal messages). + +#### Joining a customer group + +When a team member taps `/join`, the bot first verifies that the target `groupId` is a business group hosted by the main profile (i.e., has a `businessChat` property). If not, the bot replies with an error in the team group and does nothing. If valid, the bot adds the team member to the customer group (via the shared `addOrFindTeamMember` helper, which promotes to Owner at invite time via `apiSetMembersRole(Owner)`, with a fallback re-promotion on connect). From within the customer group, the team member chats directly with the customer. Their messages trigger card updates in the team group (icon change, wait time reset). The customer sees the team member as a real group participant. + +#### Edge cases + +| Situation | What happens | +|-----------|-------------| +| All team members leave before any sends a message | State stays `TEAM-PENDING` (customer is still waiting for a response). Next `/team` re-adds them silently. | +| Customer leaves | All in-memory state cleaned up; card remains (TBD) | +| No `--auto-add-team-members` (`-a`) configured | `/team` tells customer "no team members available yet" | +| Team member already in customer group | `apiListMembers` lookup finds existing member — no error | + +--- + +## 5. Architecture + +### 5.1 CLI Overview + +``` +GROK_API_KEY=... node dist/index.js --team-group "Support Team" [options] +``` + +**Environment variables:** + +| Var | Required | Purpose | +|-----|----------|---------| +| `GROK_API_KEY` | No | xAI API key for Grok. If unset or empty, the bot starts with Grok API disabled: it logs `"No GROK_API_KEY provided, disabling Grok support"`, the `/grok` command is not registered, customer-facing messages (`queueMessage`, `noTeamMembersMessage`) drop the `/grok` clause, and any `/grok` the customer types is treated as an unrecognized command. Note: `config.grokContactId` is still restored from the state file even when the API is disabled, so the one-way gate can identify and remove Grok members from groups when team takes over. When `GROK_API_KEY` is set, `--context-file` must also be provided — startup fails otherwise. | + +**CLI flags:** + +| Flag | Required | Default | Format | Purpose | +|------|----------|---------|--------|---------| +| `--db-prefix` | No | `./data/simplex` | path | Database file prefix (both profiles share it) | +| `--team-group` | Yes | — | `name` | Team group display name (auto-created if absent, resolved by persisted ID on restarts) | +| `--auto-add-team-members` / `-a` | No | `""` | `ID:name,...` | Comma-separated team member contacts. Validated at startup — exits on mismatch. Without this, `/team` tells customers no members available. | +| `--context-file` | Required when `GROK_API_KEY` set | — | path | Path to the Grok system-prompt / SimpleX documentation context file. Loaded at startup and passed as the `system` message on every Grok API call. Required when `GROK_API_KEY` is set — startup fails otherwise. When missing at runtime (file unreadable), a warning is logged and Grok runs with an empty system prompt. | +| `--timezone` | No | `"UTC"` | IANA tz | For weekend detection (24h vs 48h). Weekend is Saturday 00:00 through Sunday 23:59 in this timezone. | +| `--complete-hours` | No | `3` | number | Hours of customer inactivity after last team/Grok reply before auto-completing a conversation (✅ icon, "done" wait time). | +| `--card-flush-seconds` | No | `300` | number | Seconds between card dashboard update flushes. Lower values give faster updates; higher values reduce message churn. | + +**Why `--auto-add-team-members` (`-a`) uses `ID:name`:** Contact IDs are local to the bot's database — not discoverable externally. The bot DMs each team member their ID when they join the team group. The name is validated at startup to catch stale IDs pointing to the wrong contact. + +**Customer commands** (available as tappable buttons in customer business chats; see implementation plan §7 for the per-group lazy sync): + +| Command | Available | Effect | +|---------|-----------|--------| +| `/grok` | Before any team member sends a message, and only if `GROK_API_KEY` is set | Enter Grok mode | +| `/team` | QUEUE or GROK state | Add team members, permanently enter Team mode once any replies | + +**Unrecognized commands** are treated as normal messages in the current mode. When Grok is disabled (no `GROK_API_KEY`), `/grok` is not registered in the bot command list and, if typed manually, falls into this "unrecognized" path. + +**Team commands** (registered in team group via `groupPreferences`): + +| Command | Effect | +|---------|--------| +| `/join ` | Join the specified customer group as Owner. Card emits the clickable form `/'join '`; the handler reads `groupId` from the framework's structured command (`util.ciBotCommand → {keyword, params}`), not from regex over the message text. | + +### 5.2 Bot Architecture + +The bot process runs a single `ChatApi` instance with **two user profiles**: + +- **Main profile** — the support bot's account ("Ask SimpleX Team"). Owns the business address, hosts all business groups, communicates with customers, communicates with the team group, and controls group membership. On startup the bot checks the main profile for an existing business address via `apiGetUserAddress`; if none exists (first run), it creates one via `apiCreateBusinessAddress`. The address is stored in the SimpleX database as part of the profile — it survives restarts and state file loss without re-creation. The business address link is printed to stdout on every startup. +- **Grok profile** — the Grok agent's account (display name "Grok"). Is invited into customer groups as a Member. Sends Grok's responses so they appear to come from the Grok identity. The Grok user is created by the bot on first run via `apiCreateActiveUser` and its `userId` is persisted to `state.json` as `grokUserId`; subsequent runs look it up by ID (never by name — a renamed profile would silently break name-based matching). On startup, if the profile already exists, the bot compares its current profile (display name, image) against the desired values and calls `apiUpdateProfile()` if anything changed — this pushes the update to all Grok contacts so profile picture changes take effect immediately. + +``` +┌─────────────────────────────────────────────────┐ +│ Support Bot Process (Node.js) │ +│ │ +│ chat: ChatApi ← ChatApi.init("./data/simplex") │ +│ Single database, two user profiles │ +│ │ +│ mainUserId ← "Ask SimpleX Team" profile │ +│ • Business address, event routing, state mgmt │ +│ • Controls group membership │ +│ │ +│ grokUserId ← "Grok" profile │ +│ • Joins customer groups as Member │ +│ • Sends Grok responses into groups │ +│ │ +│ profileMutex: serialize apiSetActiveUser + call │ +│ GrokApiClient → api.x.ai/v1/chat/completions │ +└─────────────────────────────────────────────────┘ +``` + +Before each SimpleX API call, the bot switches to the appropriate profile via `apiSetActiveUser(userId)`. All profile-switching and SimpleX API calls are serialized through a mutex to prevent interleaving. The Grok HTTP API call (external network request to xAI) is made **outside** the mutex — only the profile switch + SimpleX read/send calls need serialization. This prevents a slow Grok response from blocking all other bot operations. + +**Event delivery is profile-independent.** ChatApi delivers events for all user profiles in the database, not just the active one. Every event includes a `user` field identifying which profile it belongs to. `apiSetActiveUser` only affects the context for write/send API calls — it does not filter event subscription. The bot routes events by checking `event.user`: main profile events go to the main handler, Grok profile events go to the Grok handler. + +The Grok profile is self-contained: it watches its own events (`newChatItems`, `receivedGroupInvitation`), calls the Grok HTTP API, and sends responses — all using group IDs from its own events. The main profile only controls Grok's group membership (invite/remove) and reflects Grok's responses in the team group card. + +### 5.3 Grok Integration + +Grok is not a service call hidden behind the bot's account. It is a **second user profile** within the same SimpleX Chat process and database. The customer sees messages from "Grok" as a real group participant — not from the support bot. This is what makes Grok transparent to the user. + +The Grok profile is **self-contained**: it watches its own events, reads group history through its own view, calls the Grok HTTP API, and sends responses — all using its own local group IDs from its own events. No cross-profile ID mapping is needed. + +#### Startup: establishing the bot↔Grok contact + +On first run (no state file), the bot must establish a SimpleX contact between the main and Grok profiles: + +1. Main profile creates a one-time invite link +2. Grok profile connects to it +3. The bot waits up to 60 seconds for `contactConnected` to fire +4. The resulting `grokContactId` is written to the state file + +On subsequent runs, the bot always looks up `grokContactId` from the state file and verifies it still exists in the main profile's contact list — even when `GROK_API_KEY` is not set. This ensures the one-way gate can identify and remove Grok members from groups when a team member sends a text message, preventing "phantom" Grok members that would cause dual responses if Grok is later re-enabled. If the contact is not found and Grok is enabled, it is re-established. + +#### Per-conversation: how Grok joins a group + +When a customer sends `/grok`: + +**Main profile side (failure detection):** +1. Bot sends "Inviting Grok, please wait..." to the customer group +2. Main profile: `apiAddMember(groupId, grokContactId, Member)` — invites the Grok contact to the customer's business group. If `groupDuplicateMember` (customer sent `/grok` again before join completed), the duplicate activation returns silently — the in-flight one handles the outcome. +3. The `member.memberId` is stored in an in-memory map `pendingGrokJoins: memberId → mainGroupId`. Any invitation event that arrived during the `apiAddMember` await (race condition) is drained from the buffer and processed immediately. +4. Main profile receives `connectedToGroupMember` for any member connecting in the group. The bot checks the event's `memberId` against `pendingGrokJoins` — only a match resolves the 120-second promise. This promise is only for failure detection — if it times out, the bot notifies the customer and falls back to QUEUE. + +**Grok profile side (independent, triggered by its own events):** +5. Grok profile receives a `receivedGroupInvitation` event. If a matching `pendingGrokJoins` entry exists, auto-accepts via `apiJoinGroup(groupId)`. If not (race: event arrived before step 3), buffers the event for the main profile to drain. +5. Grok profile reads visible history from the group — the last 100 messages — to build the initial Grok API context (customer messages → `user` role) +6. Grok profile calls the Grok HTTP API with this context +7. Grok profile sends the response into the group via `apiSendTextMessage([Group, groupId], response)` — visible to the customer as a message from "Grok" + +**Initial response gating:** When Grok joins a group, the message backlog may trigger per-message responses (via `newChatItems`) at the same time `activateGrok` is sending the initial combined response. To prevent duplicate replies, per-message responses are suppressed (via `grokInitialResponsePending`) until the initial combined response completes. The flag is set before `waitForGrokJoin` and cleared after the initial response is sent (or fails). Without this gate, customers would receive both individual per-message replies AND a combined initial reply — e.g. 3 replies for 2 messages. + +**Card update:** Main profile sees Grok's response as `groupRcv` and updates the team group card (same mechanism as ongoing Grok messages). + +**Visible history** must be enabled on customer groups (the bot enables it alongside file uploads in the business request handler). This allows Grok to read the full conversation history after joining, rather than only seeing messages sent after it joined. If Grok reads history and finds no customer messages (e.g., visible history was disabled or the API call failed), it sends a generic greeting asking the customer to repeat their question. + +#### Per-message: ongoing Grok conversation + +After the initial response, the Grok profile watches its own `newChatItems` events. It only triggers a Grok API call for `groupRcv` messages from the customer — identified via `businessChat.customerId` on the group's `groupInfo` (accessible to all members). Messages from the bot (main profile), from Grok itself (`groupSnd`), and from team members are ignored. Non-text messages (images, files, voice) do not trigger Grok API calls but still trigger a card update in the team group. + +**Batch deduplication:** When multiple customer messages arrive in a single `newChatItems` event (e.g., rapid messages delivered as a batch), only the last customer message per group triggers a Grok API call. Earlier messages are included in the history context via `apiGetChat`, so the single response addresses all messages in the batch. Without this, each message in the batch would trigger a separate API call, and the earlier calls would include later messages in their history — producing incoherent responses that reference messages "from the future." + +Every subsequent customer text message in a group where Grok is a member: +1. Triggers a card update in the team group (via the main profile, which sees the customer message as `groupRcv`) +2. Grok profile receives the message via its own event, rebuilds history by reading the last 100 messages from its own view of the group (Grok's messages → `assistant` role, customer's messages → `user` role) +3. Grok profile calls the Grok HTTP API and sends the response into the group using the group ID from its own event +4. Main profile sees Grok's response as `groupRcv` and updates the team group card + +In Grok mode, each customer message triggers two card updates — one on receipt (reflecting the new message and updated wait time) and one after Grok responds. This gives the team real-time visibility into active Grok conversations. + +If the Grok HTTP API call fails or times out for a per-message request, the Grok profile sends an error message into the group: "Sorry, I couldn't process that. Please try again or send /team for a human team member." Grok remains in the group and the state stays GROK — the customer can retry by sending another message. + +Grok API calls are NOT serialized per customer group in the MVP. If a new customer message arrives while a Grok API call is in flight, a second call runs concurrently — `apiGetChat` is re-read at the start of each call so history converges eventually, but two rapid messages in the same group can produce interleaved context. Cross-group calls run concurrently by design (see implementation plan §10 "Cross-group Grok parallelism"). Per-group serialization is a planned future improvement. + +#### Grok removal + +Grok is removed from the group (via main profile `apiRemoveMembers`) in three cases: +1. Team member sends their first text message in the customer group +2. Grok join fails (120-second timeout) — graceful fallback to QUEUE, bot notifies the customer +3. Customer leaves the group + +### 5.4 Persistent State + +The bot writes a single JSON file (`{dbPrefix}_state.json`) that survives restarts. It uses the same `--db-prefix` as the SimpleX database files, so the state file is always co-located with the database (e.g. `./data/simplex_state.json` alongside `./data/simplex_chat.db` and `./data/simplex_agent.db`). This ensures backups and migrations that copy the database directory also capture the bot state. + +#### Why a state file at all? + +SimpleX Chat's own database stores the full message history and group membership, but it does not store the bot's derived knowledge — things like which team group was created on first run, or which contact is the established bot↔Grok link. Per-conversation state (QUEUE/GROK/TEAM-PENDING/TEAM) is written into the customer group's `customData` at the moment the bot handles each transition — it observes its own `/grok` invite, `/team` add, team message, first customer message. Only display data (message counts, timestamps, sender names) is re-derived from chat history on demand. + +#### What is persisted and why + +| Key | Type | Why persisted | What breaks without it | +|-----|------|---------------|------------------------| +| `teamGroupId` | number | The bot creates the team group on first run; subsequent runs must find the same group | Bot creates a new empty team group on every restart; all team members lose their dashboard | +| `grokContactId` | number | Establishing a bot↔Grok contact takes up to 60 seconds and is a one-time setup | Every restart requires a 60-second re-connection; if it fails the bot exits | +| `grokUserId` | number | The bot creates the Grok user on first run; subsequent runs identify it by ID so a renamed profile cannot be silently mistaken for the main user | Startup restore (active-user recovery) and Grok profile resolution would fall back to display-name matching — fragile to any rename of the Grok profile | + +The `mainUserId` is **not** persisted — it is resolved at startup from `bot.run()`, which creates the main profile on a fresh DB and returns the user object. + +#### What is NOT persisted and why + +Per-group state (`state`, `cardItemId`, `complete`) lives in SimpleX's database as the group's `customData` — persisted there rather than in the bot's state file. + +| State | Where it lives instead | +|-------|----------------------| +| `state, cardItemId, complete` (per group) | Stored in the group's customData — conversation state, card message ID, auto-completed flag. `state` is written at event time (first customer message, `/grok`, `/team`, team's first message); the bot never re-derives it by scanning chat history. | +| Last customer message time | Derived from most recent customer message in chat history | +| Message count | Derived from message count in chat history (all messages except the bot's own) | +| Customer name | Always available from the group's display name | +| Who sent last message | Derived from recent chat history | +| `pendingGrokJoins` | In-flight during the 120-second join window only | +| Owner role promotion | Not tracked — the bot promotes team members to Owner at two idempotent points: (1) at invite time, immediately after `apiAddMember` in `addOrFindTeamMember` (skipped if the member is already in the group); (2) on every `memberConnected` in a customer group (unless the member is the customer or Grok). Survives restarts. | +| `pendingTeamDMs` | Messages queued to greet team members — simply not sent if lost | +| `grokJoinResolvers`, `grokFullyConnected` | Pure async synchronization primitives — always empty at startup | + +#### Failure modes + +If the state file is deleted or corrupted: +- A new team group is created. Team members must re-join it. +- The bot↔Grok contact is re-established (60-second startup delay). +- Grok remains in any groups it was already a member of. Since the Grok profile watches its own events, it will continue responding to customer messages in those groups without any additional recovery — no cross-profile state needs to be rebuilt. diff --git a/apps/simplex-support-bot/src/bot.ts b/apps/simplex-support-bot/src/bot.ts new file mode 100644 index 0000000000..9bfb44d93d --- /dev/null +++ b/apps/simplex-support-bot/src/bot.ts @@ -0,0 +1,920 @@ +import {api, util} from "simplex-chat" +import {T, CEvt} from "@simplex-chat/types" +import {Config} from "./config.js" +import {GrokMessage, GrokApiClient} from "./grok.js" +import {CardManager, ConversationState} from "./cards.js" +import { + queueMessage, grokInvitingMessage, grokActivatedMessage, teamAddedMessage, + teamAlreadyInvitedMessage, teamLockedMessage, noTeamMembersMessage, + grokUnavailableMessage, grokErrorMessage, grokNoHistoryMessage, +} from "./messages.js" +import {profileMutex, log, logError} from "./util.js" + +// True for any non-terminal status — invited but not yet accepted, through +// connected. Used to decide whether a contact is already in the group so we +// don't trigger a re-invite (the SimpleX API resends the invitation for a +// member in GSMemInvited). +function isInGroup(m: T.GroupMember): boolean { + switch (m.memberStatus) { + case T.GroupMemberStatus.Rejected: + case T.GroupMemberStatus.Removed: + case T.GroupMemberStatus.Left: + case T.GroupMemberStatus.Deleted: + case T.GroupMemberStatus.Unknown: + return false + default: + return true + } +} + +export class SupportBot { + // Card manager + cards: CardManager + + // Grok group mapping: memberId → mainGroupId (for pending joins) + private pendingGrokJoins = new Map() + // Buffered invitations that arrived before pendingGrokJoins was set (race condition) + private bufferedGrokInvitations = new Map() + // mainGroupId → grokLocalGroupId + private grokGroupMap = new Map() + // grokLocalGroupId → mainGroupId + private reverseGrokMap = new Map() + // mainGroupId → resolve fn for grok join + private grokJoinResolvers = new Map void>() + // mainGroupIds where Grok connectedToGroupMember fired + private grokFullyConnected = new Set() + // Suppress per-message Grok responses while activateGrok sends the initial combined response + private grokInitialResponsePending = new Set() + + // Pending DMs for team group members (contactId → message) + private pendingTeamDMs = new Map() + // Contacts that already received the team DM (dedup) + private sentTeamDMs = new Set() + + // Tracked fire-and-forget operations (for testing) + private _pendingOps: Promise[] = [] + + // Bot's business address link + businessAddress: string | null = null + + // Groups whose groupPreferences.commands we've already verified/synced + // in this process. Populated lazily by syncGroupCommands() on the first + // send to each group. + private syncedGroups = new Set() + + constructor( + private chat: api.ChatApi, + private grokApi: GrokApiClient | null, + private config: Config, + private mainUserId: number, + private grokUserId: number | null, + private desiredCommands: T.ChatBotCommand[], + ) { + this.cards = new CardManager(chat, config, mainUserId, config.cardFlushSeconds * 1000) + } + + private get grokEnabled(): boolean { + return this.grokApi !== null + } + + // Wait for all fire-and-forget operations to settle (for testing) + async flush(): Promise { + while (this._pendingOps.length > 0) { + const ops = this._pendingOps.splice(0) + await Promise.allSettled(ops) + } + } + + private fireAndForget(op: Promise): void { + const tracked = op.catch(err => logError("async operation error", err)) + this._pendingOps.push(tracked) + tracked.finally(() => { + const idx = this._pendingOps.indexOf(tracked) + if (idx >= 0) this._pendingOps.splice(idx, 1) + }) + } + + // --- Profile-switching helpers --- + + private async withMainProfile(fn: () => Promise): Promise { + return profileMutex.runExclusive(async () => { + await this.chat.apiSetActiveUser(this.mainUserId) + return fn() + }) + } + + // Ensure this group's groupPreferences.commands match desiredCommands, + // so commands in outgoing messages render as clickable for members of + // this group. Scoped to the group (apiUpdateGroupProfile broadcasts + // XGrpInfo/XGrpPrefs to group members only), and cached so we don't + // re-check on every send. Pre-checks local state via apiGetChat so we + // don't issue a no-op broadcast when the group already has the + // commands. + private async syncGroupCommands(groupId: number): Promise { + if (this.syncedGroups.has(groupId)) return + const desiredJSON = JSON.stringify(this.desiredCommands) + const chat = await this.chat.apiGetChat(T.ChatType.Group, groupId, 0) + const info = chat.chatInfo + if (info.type !== "group") return + const gp = info.groupInfo.groupProfile + const currentPrefs = gp.groupPreferences ?? {} + if (JSON.stringify(currentPrefs.commands ?? []) !== desiredJSON) { + await this.chat.apiUpdateGroupProfile(groupId, { + ...gp, + groupPreferences: {...currentPrefs, commands: this.desiredCommands}, + }) + log(`Pushed commands to group ${groupId}`) + } + this.syncedGroups.add(groupId) + } + + private async withGrokProfile(fn: () => Promise): Promise { + if (this.grokUserId === null) throw new Error("Grok is disabled (no GROK_API_KEY)") + const grokUserId = this.grokUserId + return profileMutex.runExclusive(async () => { + await this.chat.apiSetActiveUser(grokUserId) + return fn() + }) + } + + // --- Main profile event handlers --- + + async onBusinessRequest(evt: CEvt.AcceptingBusinessRequest): Promise { + const groupId = evt.groupInfo.groupId + try { + const profile = evt.groupInfo.groupProfile + await this.withMainProfile(() => + this.chat.apiUpdateGroupProfile(groupId, { + displayName: profile.displayName, + fullName: profile.fullName, + groupPreferences: { + ...profile.groupPreferences, + files: {enable: T.GroupFeatureEnabled.On}, + history: {enable: T.GroupFeatureEnabled.On}, + }, + }) + ) + // file uploads + history enabled + } catch (err) { + logError(`Failed to update business group ${groupId} preferences`, err) + } + } + + async onNewChatItems(evt: CEvt.NewChatItems): Promise { + // Only process events for main profile + if (evt.user.userId !== this.mainUserId) return + for (const ci of evt.chatItems) { + try { + await this.processMainChatItem(ci) + } catch (err) { + logError("Error processing chat item", err) + } + } + } + + async onChatItemUpdated(evt: CEvt.ChatItemUpdated): Promise { + if (evt.user.userId !== this.mainUserId) return + const {chatInfo} = evt.chatItem + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + if (!groupInfo.businessChat) return + this.cards.scheduleUpdate(groupInfo.groupId) + } + + async onChatItemReaction(evt: CEvt.ChatItemReaction): Promise { + if (evt.user.userId !== this.mainUserId) return + if (!evt.added) return + const chatInfo = evt.reaction.chatInfo + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + if (!groupInfo.businessChat) return + this.cards.scheduleUpdate(groupInfo.groupId) + } + + async onLeftMember(evt: CEvt.LeftMember): Promise { + if (evt.user.userId !== this.mainUserId) return + const groupId = evt.groupInfo.groupId + const member = evt.member + const bc = evt.groupInfo.businessChat + if (!bc) return + + if (member.memberId === bc.customerId) { + log(`Customer left group ${groupId}`) + this.cleanupGrokMaps(groupId) + try { await this.cards.clearCustomData(groupId) } catch {} + return + } + + if (this.config.grokContactId !== null && member.memberContactId === this.config.grokContactId) { + log(`Grok left group ${groupId}`) + this.cleanupGrokMaps(groupId) + return + } + + if (this.config.teamMembers.some(tm => tm.id === member.memberContactId)) { + log(`Team member left group ${groupId}`) + } + } + + async onJoinedGroupMember(evt: CEvt.JoinedGroupMember): Promise { + if (evt.user.userId !== this.mainUserId) return + if (evt.groupInfo.groupId === this.config.teamGroup.id) { + await this.sendTeamMemberDM(evt.member) + } + } + + async onMemberConnected(evt: CEvt.ConnectedToGroupMember): Promise { + if (evt.user.userId !== this.mainUserId) return + const groupId = evt.groupInfo.groupId + + // Team group → send DM (if not already sent by onJoinedGroupMember) + if (groupId === this.config.teamGroup.id) { + await this.sendTeamMemberDM(evt.member, evt.memberContact) + return + } + + // Customer group → promote to Owner (unless customer or Grok). Idempotent per plan §11. + const bc = evt.groupInfo.businessChat + if (bc) { + const isCustomer = evt.member.memberId === bc.customerId + const isGrok = this.config.grokContactId !== null + && evt.member.memberContactId === this.config.grokContactId + if (!isCustomer && !isGrok) { + try { + await this.withMainProfile(() => + this.chat.apiSetMembersRole(groupId, [evt.member.groupMemberId], T.GroupMemberRole.Owner) + ) + log(`Promoted member ${evt.member.groupMemberId} to Owner in group ${groupId}`) + } catch (err) { + logError(`Failed to promote member in group ${groupId}`, err) + } + } + } + } + + async onMemberContactReceivedInv(evt: CEvt.NewMemberContactReceivedInv): Promise { + if (evt.user.userId !== this.mainUserId) return + const {contact, groupInfo, member} = evt + if (groupInfo.groupId === this.config.teamGroup.id) { + if (this.sentTeamDMs.has(contact.contactId)) return + log(`DM contact from team group member: ${contact.contactId}:${member.memberProfile.displayName}`) + const name = member.memberProfile.displayName + const formatted = name.includes(" ") ? `'${name}'` : name + const msg = `Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is ${contact.contactId}:${formatted}` + // Try sending immediately — contact may already be usable + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Direct, contact.contactId], msg) + ) + this.sentTeamDMs.add(contact.contactId) + log(`Sent DM to team member ${contact.contactId}:${name}`) + } catch { + // Not ready yet — queue for contactConnected / contactSndReady + this.pendingTeamDMs.set(contact.contactId, msg) + log(`Queued DM for team member ${contact.contactId}:${name}`) + } + } + } + + async onContactConnected(evt: CEvt.ContactConnected): Promise { + if (evt.user.userId !== this.mainUserId) return + await this.deliverPendingDM(evt.contact.contactId) + } + + async onContactSndReady(evt: CEvt.ContactSndReady): Promise { + if (evt.user.userId !== this.mainUserId) return + await this.deliverPendingDM(evt.contact.contactId) + } + + private async deliverPendingDM(contactId: number): Promise { + if (this.sentTeamDMs.has(contactId)) { + this.pendingTeamDMs.delete(contactId) + return + } + const pendingMsg = this.pendingTeamDMs.get(contactId) + if (pendingMsg === undefined) return + this.pendingTeamDMs.delete(contactId) + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Direct, contactId], pendingMsg) + ) + this.sentTeamDMs.add(contactId) + log(`Sent DM to team member ${contactId}`) + } catch (err) { + logError(`Failed to send DM to team member ${contactId}`, err) + } + } + + // --- Grok profile event handlers --- + + async onGrokGroupInvitation(evt: CEvt.ReceivedGroupInvitation): Promise { + if (evt.user.userId !== this.grokUserId) return + const memberId = evt.groupInfo.membership.memberId + const mainGroupId = this.pendingGrokJoins.get(memberId) + if (mainGroupId === undefined) { + // Buffer: invitation may arrive before pendingGrokJoins is set (race with apiAddMember) + this.bufferedGrokInvitations.set(memberId, evt) + return + } + this.pendingGrokJoins.delete(memberId) + this.bufferedGrokInvitations.delete(memberId) + await this.processGrokInvitation(evt, mainGroupId) + } + + private async processGrokInvitation(evt: CEvt.ReceivedGroupInvitation, mainGroupId: number): Promise { + log(`Grok joining group: mainGroupId=${mainGroupId}, grokGroupId=${evt.groupInfo.groupId}`) + try { + await this.withGrokProfile(() => this.chat.apiJoinGroup(evt.groupInfo.groupId)) + } catch (err) { + logError(`Grok failed to join group ${evt.groupInfo.groupId}`, err) + return + } + this.grokGroupMap.set(mainGroupId, evt.groupInfo.groupId) + this.reverseGrokMap.set(evt.groupInfo.groupId, mainGroupId) + } + + async onGrokMemberConnected(evt: CEvt.ConnectedToGroupMember): Promise { + if (evt.user.userId !== this.grokUserId) return + const grokGroupId = evt.groupInfo.groupId + const mainGroupId = this.reverseGrokMap.get(grokGroupId) + if (mainGroupId === undefined) return + this.grokFullyConnected.add(mainGroupId) + const resolver = this.grokJoinResolvers.get(mainGroupId) + if (resolver) { + this.grokJoinResolvers.delete(mainGroupId) + log(`Grok fully connected: mainGroupId=${mainGroupId}, grokGroupId=${grokGroupId}`) + resolver() + } + } + + async onGrokNewChatItems(evt: CEvt.NewChatItems): Promise { + if (evt.user.userId !== this.grokUserId) return + // When multiple customer messages arrive in one batch, only respond to the + // last per group — earlier messages are included in its history context. + const lastPerGroup = new Map() + for (const ci of evt.chatItems) { + const {chatInfo, chatItem} = ci + if (chatInfo.type !== "group") continue + if (chatItem.chatDir.type !== "groupRcv") continue + if (!util.ciContentText(chatItem)?.trim()) continue + if (util.ciBotCommand(chatItem)) continue + const bc = chatInfo.groupInfo.businessChat + if (!bc) continue + if (chatItem.chatDir.groupMember.memberId !== bc.customerId) continue + lastPerGroup.set(chatInfo.groupInfo.groupId, ci) + } + // Groups are independent — avoid serializing one group's xAI latency across the others. + await Promise.allSettled( + [...lastPerGroup.values()].map((ci) => this.processGrokChatItem(ci)), + ) + } + + // --- Main profile message routing --- + + private async processMainChatItem(ci: T.AChatItem): Promise { + const {chatInfo, chatItem} = ci + + // 1. Direct text message → reply with business address + if (chatInfo.type === "direct" && chatItem.chatDir.type === "directRcv" + && (chatItem.content as any).type === "rcvMsgContent") { + if (this.businessAddress) { + const contactId = chatInfo.contact.contactId + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage( + [T.ChatType.Direct, contactId], + `Please re-connect to this address for any questions: ${this.businessAddress}`, + ) + ) + } catch (err) { + logError(`Failed to reply to direct message from contact ${contactId}`, err) + } + } + return + } + + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + const groupId = groupInfo.groupId + + // 2. Team group → handle /join + if (groupId === this.config.teamGroup.id) { + await this.processTeamGroupMessage(chatItem) + return + } + + // 3. Skip non-business groups + if (!groupInfo.businessChat) return + + // 4. Skip own messages + if (chatItem.chatDir.type === "groupSnd") return + if (chatItem.chatDir.type !== "groupRcv") return + + const sender = chatItem.chatDir.groupMember + const bc = groupInfo.businessChat + const isCustomer = sender.memberId === bc.customerId + + // 6. Non-customer message → one-way gate check + card update + if (!isCustomer) { + const isTeam = this.config.teamMembers.some(tm => tm.id === sender.memberContactId) + + if (isTeam && util.ciContentText(chatItem)?.trim()) { + // One-way gate: first team text → transition to TEAM + remove Grok + const data = await this.cards.getRawCustomData(groupId) + if (data?.state !== "TEAM") { + await this.cards.mergeCustomData(groupId, {state: "TEAM"}) + const {grokMember} = await this.cards.getGroupComposition(groupId) + if (grokMember) { + log(`One-way gate: team message in group ${groupId}, removing Grok`) + try { + await this.withMainProfile(() => + this.chat.apiRemoveMembers(groupId, [grokMember.groupMemberId]) + ) + } catch { + // may have already left + } + this.cleanupGrokMaps(groupId) + } + } + } + // Schedule card update for any non-customer message (team or Grok) + this.cards.scheduleUpdate(groupId) + return + } + + // 8. Customer message → derive state and dispatch + const state = await this.cards.deriveState(groupId) + const rawCmd = util.ciBotCommand(chatItem) + // When Grok is disabled, ignore /grok so it behaves like an unknown command + const cmd = rawCmd?.keyword === "grok" && !this.grokEnabled ? null : rawCmd + const text = util.ciContentText(chatItem)?.trim() || null + + switch (state) { + case "WELCOME": + if (cmd?.keyword === "grok") { + // WELCOME → GROK (skip queue msg). Write state optimistically so the + // card renders with GROK icon/label; activateGrok will revert via + // setStateOnFail if activation fails. + // Fire-and-forget: activateGrok awaits future events (waitForGrokJoin) + // which would deadlock the sequential event loop if awaited here. + await this.cards.mergeCustomData(groupId, {state: "GROK"}) + await this.cards.createCard(groupId, groupInfo) + this.fireAndForget(this.activateGrok(groupId, {sendQueueOnFail: true, setStateOnFail: "QUEUE"})) + return + } + if (cmd?.keyword === "team") { + // activateTeam writes state=TEAM-PENDING before the add loop + await this.activateTeam(groupId) + await this.cards.createCard(groupId, groupInfo) + return + } + // First regular message → QUEUE + if (text) { + await this.cards.mergeCustomData(groupId, {state: "QUEUE"}) + await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + await this.cards.createCard(groupId, groupInfo) + } + break + + case "QUEUE": + if (cmd?.keyword === "grok") { + // Write state optimistically; activateGrok reverts to QUEUE on failure + await this.cards.mergeCustomData(groupId, {state: "GROK"}) + this.fireAndForget(this.activateGrok(groupId, {setStateOnFail: "QUEUE"})) + } else if (cmd?.keyword === "team") { + await this.activateTeam(groupId) + } + this.cards.scheduleUpdate(groupId) + break + + case "GROK": + if (cmd?.keyword === "team") { + await this.activateTeam(groupId) + } else if (cmd?.keyword === "grok") { + // Already in grok mode — ignore + } else if (text) { + // Customer text → Grok responds (handled by Grok profile's onGrokNewChatItems) + // Just schedule card update for the customer message + } + this.cards.scheduleUpdate(groupId) + break + + case "TEAM-PENDING": + if (cmd?.keyword === "grok") { + // Invite Grok if not present; state stays TEAM-PENDING + const {grokMember} = await this.cards.getGroupComposition(groupId) + if (!grokMember) { + this.fireAndForget(this.activateGrok(groupId)) + } + // else: already present, ignore + } else if (cmd?.keyword === "team") { + // activateTeam handles "already invited" reply (team still present) + // or silent re-add (team has all left) + await this.activateTeam(groupId) + } + this.cards.scheduleUpdate(groupId) + break + + case "TEAM": + if (cmd?.keyword === "grok") { + await this.sendToGroup(groupId, teamLockedMessage) + } else if (cmd?.keyword === "team") { + // Team still present → "already invited"; team all left → silent re-add + await this.activateTeam(groupId) + } + this.cards.scheduleUpdate(groupId) + break + } + } + + // --- Grok profile message processing --- + + private async processGrokChatItem(ci: T.AChatItem): Promise { + if (!this.grokApi) return + const grokApi = this.grokApi + const {chatInfo, chatItem} = ci + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + const grokGroupId = groupInfo.groupId + + // Skip while activateGrok is sending the initial combined response + const mainGroupId = this.reverseGrokMap.get(grokGroupId) + if (mainGroupId !== undefined && this.grokInitialResponsePending.has(mainGroupId)) return + + // Only process received text messages from customer + if (chatItem.chatDir.type !== "groupRcv") return + const text = util.ciContentText(chatItem)?.trim() + if (!text) return // ignore non-text + + // Ignore bot commands + if (util.ciBotCommand(chatItem)) return + + // Only respond in business groups (survives restart without in-memory maps) + const bc = groupInfo.businessChat + if (!bc) return + + // Only respond to customer messages, not bot or team messages + if (chatItem.chatDir.groupMember.memberId !== bc.customerId) return + + // Read history from Grok's own view + try { + const chat = await this.withGrokProfile(() => + this.chat.apiGetChat(T.ChatType.Group, grokGroupId, 100) + ) + const history: GrokMessage[] = [] + for (const histCi of chat.chatItems) { + const histText = util.ciContentText(histCi)?.trim() + if (!histText) continue + if (histCi.chatDir.type === "groupSnd") { + history.push({role: "assistant", content: histText}) + } else if (histCi.chatDir.type === "groupRcv" + && histCi.chatDir.groupMember.memberId === bc.customerId + && !util.ciBotCommand(histCi)) { + history.push({role: "user", content: histText}) + } + } + + // Don't include the current message in history — it's the userMessage + if (history.length > 0 && history[history.length - 1].role === "user" + && history[history.length - 1].content === text) { + history.pop() + } + + // Call Grok API (outside mutex) + const response = await grokApi.chat(history, text) + + // Send response via Grok profile + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokGroupId], response) + ) + } catch (err) { + logError(`Grok per-message error for grokGroup ${grokGroupId}`, err) + try { + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokGroupId], grokErrorMessage) + ) + } catch {} + } + + // Card update scheduled by main profile seeing the groupRcv events + } + + // --- Grok activation --- + + private async activateGrok( + groupId: number, + opts: {sendQueueOnFail?: boolean; setStateOnFail?: ConversationState} = {}, + ): Promise { + if (!this.grokApi) return + const grokApi = this.grokApi + const revertStateOnFail = async () => { + if (!opts.setStateOnFail) return + const current = await this.cards.getRawCustomData(groupId) + if (current?.state !== "GROK") return + await this.cards.mergeCustomData(groupId, {state: opts.setStateOnFail}) + } + if (this.config.grokContactId === null) { + await revertStateOnFail() + await this.sendToGroup(groupId, grokUnavailableMessage) + if (opts.sendQueueOnFail) await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + this.cards.scheduleUpdate(groupId) + return + } + + // Pre-check: silent return if Grok is already in the group in any + // non-terminal status. The apiAddMember/groupDuplicateMember catch below + // handles Connected/etc. but the SimpleX API resends the invitation for + // GSMemInvited (no error thrown), so without this check a /grok issued + // while a previous activation is still pending would re-trigger the invite. + const grokMembers = await this.withMainProfile(() => this.chat.apiListMembers(groupId)) + if (grokMembers.some(m => m.memberContactId === this.config.grokContactId && isInGroup(m))) { + return + } + + // Gate MUST be up before apiAddMember / pendingGrokJoins / reverseGrokMap — + // any later and onGrokNewChatItems can fire a duplicate per-message reply. + this.grokInitialResponsePending.add(groupId) + try { + await this.sendToGroup(groupId, grokInvitingMessage) + + let member: T.GroupMember + try { + member = await this.withMainProfile(() => + this.chat.apiAddMember(groupId, this.config.grokContactId!, T.GroupMemberRole.Member) + ) + } catch (err: unknown) { + const chatErr = err as {chatError?: {errorType?: {type?: string}}} + if (chatErr?.chatError?.errorType?.type === "groupDuplicateMember") { + // Grok already in group (e.g. customer sent /grok again before join completed) — + // the in-flight activation will handle the outcome, just return silently + return + } + logError(`Failed to invite Grok to group ${groupId}`, err) + await revertStateOnFail() + await this.sendToGroup(groupId, grokUnavailableMessage) + if (opts.sendQueueOnFail) await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + this.cards.scheduleUpdate(groupId) + return + } + + this.pendingGrokJoins.set(member.memberId, groupId) + + // Drain buffered invitation that arrived during the apiAddMember await + const buffered = this.bufferedGrokInvitations.get(member.memberId) + if (buffered) { + this.bufferedGrokInvitations.delete(member.memberId) + this.pendingGrokJoins.delete(member.memberId) + await this.processGrokInvitation(buffered, groupId) + } + + const joined = await this.waitForGrokJoin(groupId, 120_000) + if (!joined) { + this.pendingGrokJoins.delete(member.memberId) + try { + await this.withMainProfile(() => + this.chat.apiRemoveMembers(groupId, [member.groupMemberId]) + ) + } catch {} + this.cleanupGrokMaps(groupId) + await revertStateOnFail() + await this.sendToGroup(groupId, grokUnavailableMessage) + if (opts.sendQueueOnFail) await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + this.cards.scheduleUpdate(groupId) + return + } + + await this.sendToGroup(groupId, grokActivatedMessage) + + // Grok joined — send initial response based on customer's accumulated messages + try { + const grokLocalGId = this.grokGroupMap.get(groupId) + if (grokLocalGId === undefined) { + await this.sendToGroup(groupId, grokUnavailableMessage) + return + } + + // Read history from Grok's own view — only customer messages. + // The previous `grokBc && ...` short-circuit let bot and team + // messages through when Grok's view had no businessChat; require + // grokBc.customerId to be present and match strictly. + const chat = await this.withGrokProfile(() => + this.chat.apiGetChat(T.ChatType.Group, grokLocalGId, 100) + ) + const grokBc = chat.chatInfo.type === "group" ? chat.chatInfo.groupInfo.businessChat : null + const customerMessages: string[] = [] + for (const ci of chat.chatItems) { + if (ci.chatDir.type !== "groupRcv") continue + if (!grokBc || ci.chatDir.groupMember.memberId !== grokBc.customerId) continue + const t = util.ciContentText(ci)?.trim() + if (t && !util.ciBotCommand(ci)) customerMessages.push(t) + } + + if (customerMessages.length === 0) { + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokLocalGId], grokNoHistoryMessage) + ) + return + } + + const initialMsg = customerMessages.join("\n") + const response = await grokApi.chat([], initialMsg) + + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokLocalGId], response) + ) + } catch (err) { + logError(`Grok initial response failed for group ${groupId}`, err) + await this.sendToGroup(groupId, grokUnavailableMessage) + } + } finally { + this.grokInitialResponsePending.delete(groupId) + } + } + + // --- Team activation --- + + private async activateTeam(groupId: number): Promise { + if (this.config.teamMembers.length === 0) { + await this.sendToGroup(groupId, noTeamMembersMessage(this.grokEnabled)) + return + } + + const data = await this.cards.getRawCustomData(groupId) + const alreadyActivated = data?.state === "TEAM-PENDING" || data?.state === "TEAM" + if (alreadyActivated) { + const {teamMembers} = await this.cards.getGroupComposition(groupId) + if (teamMembers.length > 0) { + await this.sendToGroup(groupId, teamAlreadyInvitedMessage) + return + } + // Team previously activated but all team members have since left — + // re-add silently (no teamAddedMessage). State stays TEAM-PENDING/TEAM. + for (const tm of this.config.teamMembers) { + try { + await this.addOrFindTeamMember(groupId, tm.id) + } catch (err) { + logError(`Failed to add team member ${tm.id} to group ${groupId}`, err) + } + } + return + } + + // First activation — write state BEFORE add loop so concurrent customer + // events observing mid-flight see TEAM-PENDING rather than stale state. + await this.cards.mergeCustomData(groupId, {state: "TEAM-PENDING"}) + + for (const tm of this.config.teamMembers) { + try { + await this.addOrFindTeamMember(groupId, tm.id) + } catch (err) { + logError(`Failed to add team member ${tm.id} to group ${groupId}`, err) + } + } + + const {grokMember} = await this.cards.getGroupComposition(groupId) + await this.sendToGroup(groupId, teamAddedMessage(this.config.timezone, !!grokMember)) + } + + // --- Team group commands --- + + private async processTeamGroupMessage(chatItem: T.ChatItem): Promise { + if (chatItem.chatDir.type !== "groupRcv") return + const senderContactId = chatItem.chatDir.groupMember.memberContactId + if (!senderContactId) return + + const cmd = util.ciBotCommand(chatItem) + if (cmd?.keyword !== "join") return + + const targetGroupId = Number.parseInt(cmd.params, 10) + if (Number.isNaN(targetGroupId) || targetGroupId <= 0) { + await this.sendToGroup(this.config.teamGroup.id, `Error: invalid group id "${cmd.params}"`) + return + } + await this.handleJoinCommand(targetGroupId, senderContactId) + } + + private async handleJoinCommand(targetGroupId: number, senderContactId: number): Promise { + // Validate target is a business group + const groups = await this.withMainProfile(() => + this.chat.apiListGroups(this.mainUserId) + ) + const targetGroup = groups.find(g => g.groupId === targetGroupId) + if (!targetGroup?.businessChat) { + await this.sendToGroup(this.config.teamGroup.id, `Error: group ${targetGroupId} is not a business chat`) + return + } + + try { + const member = await this.addOrFindTeamMember(targetGroupId, senderContactId) + if (member) { + log(`Team member ${senderContactId} joined group ${targetGroupId} via /join`) + } + } catch (err) { + logError(`/join failed for group ${targetGroupId}`, err) + await this.sendToGroup(this.config.teamGroup.id, `Error joining group ${targetGroupId}`) + } + } + + // --- Helpers --- + + private async addOrFindTeamMember(groupId: number, teamContactId: number): Promise { + // Pre-check membership: skip apiAddMember entirely if the contact is in + // the group in any non-terminal status. The SimpleX API resends the + // invitation for a member in GSMemInvited, so calling apiAddMember on a + // pending invitee would re-trigger an invite notification. + const members = await this.withMainProfile(() => this.chat.apiListMembers(groupId)) + const existing = members.find(m => m.memberContactId === teamContactId && isInGroup(m)) + if (existing) return existing + const member = await this.withMainProfile(() => + this.chat.apiAddMember(groupId, teamContactId, T.GroupMemberRole.Member) + ) + try { + await this.withMainProfile(() => + this.chat.apiSetMembersRole(groupId, [member.groupMemberId], T.GroupMemberRole.Owner) + ) + } catch { + // Not yet connected — will be promoted in onMemberConnected + } + return member + } + + async sendToGroup(groupId: number, text: string): Promise { + try { + await this.withMainProfile(async () => { + await this.syncGroupCommands(groupId) + await this.chat.apiSendTextMessage([T.ChatType.Group, groupId], text) + }) + } catch (err) { + logError(`Failed to send message to group ${groupId}`, err) + } + } + + private waitForGrokJoin(groupId: number, timeout: number): Promise { + if (this.grokFullyConnected.has(groupId)) return Promise.resolve(true) + return new Promise((resolve) => { + const timer = setTimeout(() => { + this.grokJoinResolvers.delete(groupId) + resolve(false) + }, timeout) + this.grokJoinResolvers.set(groupId, () => { + clearTimeout(timer) + resolve(true) + }) + }) + } + + private async sendTeamMemberDM(member: T.GroupMember, memberContact?: T.Contact): Promise { + const name = member.memberProfile.displayName + const formatted = name.includes(" ") ? `'${name}'` : name + + let contactId = memberContact?.contactId ?? member.memberContactId + if (!contactId) { + // No DM contact yet — create one and send invitation with message + try { + const contact = await this.withMainProfile(() => + this.chat.apiCreateMemberContact(this.config.teamGroup.id, member.groupMemberId) + ) + contactId = contact.contactId as number + log(`Created DM contact ${contactId} for team member ${name}`) + } catch (err) { + logError(`Failed to create member contact for ${name}`, err) + return + } + if (this.sentTeamDMs.has(contactId)) return + const msg = `Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is ${contactId}:${formatted}` + try { + await this.withMainProfile(() => + this.chat.apiSendMemberContactInvitation(contactId!, msg) + ) + this.sentTeamDMs.add(contactId) + this.pendingTeamDMs.delete(contactId) + log(`Sent DM invitation to team member ${contactId}:${name}`) + } catch { + this.pendingTeamDMs.set(contactId, msg) + } + return + } + // Contact already exists — send via normal DM + if (this.sentTeamDMs.has(contactId)) return + const msg = `Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is ${contactId}:${formatted}` + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Direct, contactId], msg) + ) + this.sentTeamDMs.add(contactId) + this.pendingTeamDMs.delete(contactId) + log(`Sent DM to team member ${contactId}:${name}`) + } catch { + this.pendingTeamDMs.set(contactId, msg) + } + } + + private cleanupGrokMaps(groupId: number): void { + const grokLocalGId = this.grokGroupMap.get(groupId) + this.grokFullyConnected.delete(groupId) + this.grokInitialResponsePending.delete(groupId) + if (grokLocalGId === undefined) return + this.grokGroupMap.delete(groupId) + this.reverseGrokMap.delete(grokLocalGId) + } +} diff --git a/apps/simplex-support-bot/src/cards.ts b/apps/simplex-support-bot/src/cards.ts new file mode 100644 index 0000000000..3d27c036e9 --- /dev/null +++ b/apps/simplex-support-bot/src/cards.ts @@ -0,0 +1,473 @@ +import {T} from "@simplex-chat/types" +import {api, util} from "simplex-chat" +import {Mutex} from "async-mutex" +import {Config} from "./config.js" +import {profileMutex, log, logError} from "./util.js" + +// State derivation types +export type ConversationState = "WELCOME" | "QUEUE" | "GROK" | "TEAM-PENDING" | "TEAM" + +function isConversationState(x: unknown): x is ConversationState { + return x === "WELCOME" || x === "QUEUE" || x === "GROK" || x === "TEAM-PENDING" || x === "TEAM" +} + +export interface GroupComposition { + grokMember: T.GroupMember | undefined + teamMembers: T.GroupMember[] +} + +interface CardData { + state?: ConversationState + cardItemId?: number + complete?: boolean +} + +function isActiveMember(m: T.GroupMember): boolean { + return m.memberStatus === T.GroupMemberStatus.Connected + || m.memberStatus === T.GroupMemberStatus.Complete + || m.memberStatus === T.GroupMemberStatus.Announced +} + +// Prevent ! from triggering SimpleX markdown styled text (color/small). +// The parser treats !N as color markup (N: 1-6, r, g, b, y, c, m, -) +// and closes at the next !. No escape mechanism exists in the parser, +// so we insert a zero-width space to break the trigger pattern. +function escapeStyledMarkdown(text: string): string { + return text.replace(/!([1-6rgbycm-])/g, "!\u200B$1") +} + +// Truncate a single message to ~maxChars, appending [truncated] if needed +function truncateMsg(text: string, maxChars: number): string { + if (text.length <= maxChars) return text + return text.slice(0, maxChars) + "… [truncated]" +} + +// Describe non-text content types +function contentTypeLabel(ci: T.ChatItem): string | null { + const content = ci.content as T.CIContent + if (content.type !== "rcvMsgContent" && content.type !== "sndMsgContent") return null + const mc = content.msgContent + switch (mc.type) { + case "image": return "[image]" + case "video": return "[video]" + case "voice": return "[voice]" + case "file": return "[file]" + default: return null + } +} + +export class CardManager { + private pendingUpdates = new Set() + private flushInterval: NodeJS.Timeout + // Outer lock; profileMutex (via withMainProfile) is the inner lock. + private customDataMutexes = new Map() + + constructor( + private chat: api.ChatApi, + private config: Config, + private mainUserId: number, + flushIntervalMs = 300 * 1000, + ) { + this.flushInterval = setInterval(() => this.flush(), flushIntervalMs) + this.flushInterval.unref() + } + + private async withMainProfile(fn: () => Promise): Promise { + return profileMutex.runExclusive(async () => { + await this.chat.apiSetActiveUser(this.mainUserId) + return fn() + }) + } + + private getCustomDataMutex(groupId: number): Mutex { + let m = this.customDataMutexes.get(groupId) + if (!m) { + m = new Mutex() + this.customDataMutexes.set(groupId, m) + } + return m + } + + scheduleUpdate(groupId: number): void { + this.pendingUpdates.add(groupId) + } + + async createCard(groupId: number, groupInfo: T.GroupInfo): Promise { + const {text} = await this.composeCard(groupId, groupInfo) + const chatRef: T.ChatRef = {chatType: T.ChatType.Group, chatId: this.config.teamGroup.id} + const items = await this.withMainProfile(() => + this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + ) + await this.mergeCustomData(groupId, {cardItemId: items[0].chatItem.meta.itemId}) + } + + async flush(): Promise { + const groups = [...this.pendingUpdates] + this.pendingUpdates.clear() + for (const groupId of groups) { + try { + await this.flushOne(groupId) + } catch (err) { + logError(`Card flush failed for group ${groupId}`, err) + } + } + } + + // Dispatches to create-path when cardItemId is absent so a failed createCard retries. + private async flushOne(groupId: number): Promise { + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const groupInfo = groups.find(g => g.groupId === groupId) + if (!groupInfo) return + const data = groupInfo.customData as Record | undefined + if (typeof data?.cardItemId === "number") { + await this.updateCard(groupId) + } else { + await this.createCard(groupId, groupInfo) + } + } + + async refreshAllCards(): Promise { + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const activeCards: {groupId: number; cardItemId: number}[] = [] + for (const group of groups) { + const customData = group.customData as Record | undefined + if (customData && typeof customData.cardItemId === "number" && !customData.complete) { + activeCards.push({groupId: group.groupId, cardItemId: customData.cardItemId}) + } + } + if (activeCards.length === 0) return + + // Sort ascending by cardItemId — higher ID = more recently updated card. + // Oldest-updated cards refresh first; newest-updated refresh last, + // so the most recent cards end up at the bottom of the team group. + activeCards.sort((a, b) => a.cardItemId - b.cardItemId) + + log(`Startup: refreshing ${activeCards.length} card(s)`) + + for (const {groupId} of activeCards) { + try { + await this.updateCard(groupId) + } catch (err) { + logError(`Startup card refresh failed for group ${groupId}`, err) + } + } + } + + destroy(): void { + clearInterval(this.flushInterval) + } + + // --- State derivation --- + + async getGroupComposition(groupId: number): Promise { + const members = await this.withMainProfile(() => this.chat.apiListMembers(groupId)) + return { + grokMember: members.find(m => + this.config.grokContactId !== null + && m.memberContactId === this.config.grokContactId + && isActiveMember(m)), + teamMembers: members.filter(m => + this.config.teamMembers.some(tm => tm.id === m.memberContactId) + && isActiveMember(m)), + } + } + + async deriveState(groupId: number): Promise { + const data = await this.getRawCustomData(groupId) + return data?.state ?? "WELCOME" + } + + async getLastCustomerMessageTime(groupId: number, customerId: string): Promise { + const chat = await this.getChat(groupId, 20) + for (let i = chat.chatItems.length - 1; i >= 0; i--) { + const ci = chat.chatItems[i] + if (ci.chatDir.type === "groupRcv" && ci.chatDir.groupMember.memberId === customerId) { + return new Date(ci.meta.createdAt).getTime() + } + } + return undefined + } + + async getLastTeamOrGrokMessageTime(groupId: number): Promise { + const chat = await this.getChat(groupId, 20) + for (let i = chat.chatItems.length - 1; i >= 0; i--) { + const ci = chat.chatItems[i] + if (ci.chatDir.type === "groupRcv") { + const contactId = ci.chatDir.groupMember.memberContactId + const isTeam = this.config.teamMembers.some(tm => tm.id === contactId) + const isGrok = this.config.grokContactId !== null && contactId === this.config.grokContactId + if (isTeam || isGrok) return new Date(ci.meta.createdAt).getTime() + } + if (ci.chatDir.type === "groupSnd") { + // Bot's own messages don't count + } + } + return undefined + } + + // --- Custom data --- + + async getRawCustomData(groupId: number): Promise | null> { + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const group = groups.find(g => g.groupId === groupId) + if (!group?.customData) return null + const data = group.customData as Record + const result: Partial = {} + if (isConversationState(data.state)) result.state = data.state + if (typeof data.cardItemId === "number") result.cardItemId = data.cardItemId + if (data.complete === true) result.complete = true + return result + } + + async mergeCustomData(groupId: number, patch: Partial): Promise { + return this.getCustomDataMutex(groupId).runExclusive(async () => { + const current = (await this.getRawCustomData(groupId)) ?? {} + const merged: Partial = {...current, ...patch} + for (const key of Object.keys(merged) as (keyof CardData)[]) { + if (merged[key] === undefined) delete merged[key] + } + await this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId, merged)) + }) + } + + async clearCustomData(groupId: number): Promise { + return this.getCustomDataMutex(groupId).runExclusive(() => + this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId)) + ) + } + + // --- Chat history access --- + + async getChat(groupId: number, count: number): Promise { + return this.withMainProfile(() => this.chat.apiGetChat(T.ChatType.Group, groupId, count)) + } + + // --- Internal --- + + private async updateCard(groupId: number): Promise { + // Read customData and groupInfo in one apiListGroups call + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const groupInfo = groups.find(g => g.groupId === groupId) + if (!groupInfo) return + + const customData = groupInfo.customData as Record | undefined + const cardItemId = customData?.cardItemId + if (typeof cardItemId !== "number") return + + try { + await this.withMainProfile(() => + this.chat.apiDeleteChatItems( + T.ChatType.Group, this.config.teamGroup.id, [cardItemId], T.CIDeleteMode.Broadcast + ) + ) + } catch { + // card may already be deleted + } + + const {text, complete} = await this.composeCard(groupId, groupInfo) + const chatRef: T.ChatRef = {chatType: T.ChatType.Group, chatId: this.config.teamGroup.id} + const items = await this.withMainProfile(() => + this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + ) + const patch: Partial = { + cardItemId: items[0].chatItem.meta.itemId, + complete: complete ? true : undefined, + } + await this.mergeCustomData(groupId, patch) + } + + private async composeCard(groupId: number, groupInfo: T.GroupInfo): Promise<{text: string, complete: boolean}> { + const rawName = groupInfo.groupProfile.displayName || `group-${groupId}` + const customerName = rawName.replace(/\n+/g, " ") + const bc = groupInfo.businessChat + const customerId = bc?.customerId + + const state = await this.deriveState(groupId) + const {teamMembers} = await this.getGroupComposition(groupId) + + const icon = await this.computeIcon(groupId, state, customerId ?? undefined) + const waitStr = await this.computeWaitTime(groupId, state, customerId ?? undefined) + + const chat = await this.getChat(groupId, 100) + const msgCount = chat.chatItems.filter((ci: T.ChatItem) => ci.chatDir.type !== "groupSnd").length + + const stateLabel = this.stateLabel(state) + + const agentNames = teamMembers.map(m => m.memberProfile.displayName) + const agentStr = agentNames.length > 0 ? ` · ${agentNames.join(", ")}` : "" + + const preview = this.buildPreview(chat.chatItems, customerName, customerId) + + // Final line uses /'join ' quoting so SimpleX clients render the full + // command (including the argument) as a single clickable token. + const joinCmd = `/'join ${groupId}'` + + const line1 = `${icon} *${customerName}* · ${waitStr} · ${msgCount} msgs` + const line2 = `${stateLabel}${agentStr}` + return {text: `${line1}\n${line2}\n${preview}\n${joinCmd}`, complete: icon === "✅"} + } + + private async computeIcon( + groupId: number, state: ConversationState, customerId?: string, + ): Promise { + const now = Date.now() + const completeMs = this.config.completeHours * 3600_000 + + // Check auto-complete: last team/Grok message time vs customer silence + const lastTeamGrokTime = await this.getLastTeamOrGrokMessageTime(groupId) + if (lastTeamGrokTime) { + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + // Auto-complete if team/grok replied and customer hasn't responded since, for completeHours + if (!lastCustTime || lastCustTime < lastTeamGrokTime) { + if (now - lastTeamGrokTime >= completeMs) return "✅" + } + } + + switch (state) { + case "QUEUE": { + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (!lastCustTime) return "🟡" + const waitMs = now - lastCustTime + if (waitMs < 5 * 60_000) return "🆕" + if (waitMs < 2 * 3600_000) return "🟡" + return "🔴" + } + case "GROK": + return "🤖" + case "TEAM-PENDING": + return "👋" + case "TEAM": { + // Check if customer follow-up unanswered > 2h + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (lastCustTime && lastTeamGrokTime && lastCustTime > lastTeamGrokTime) { + return (now - lastCustTime > 2 * 3600_000) ? "⏰" : "💬" + } + return "💬" + } + default: + return "🟡" + } + } + + private async computeWaitTime( + groupId: number, _state: ConversationState, customerId?: string, + ): Promise { + const now = Date.now() + const completeMs = this.config.completeHours * 3600_000 + + const lastTeamGrokTime = await this.getLastTeamOrGrokMessageTime(groupId) + if (lastTeamGrokTime) { + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (!lastCustTime || lastCustTime < lastTeamGrokTime) { + if (now - lastTeamGrokTime >= completeMs) return "done" + } + } + + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (!lastCustTime) return "<1m" + return this.formatDuration(now - lastCustTime) + } + + private stateLabel(state: ConversationState): string { + switch (state) { + case "QUEUE": return "Queue" + case "GROK": return "Grok" + case "TEAM-PENDING": return "Team – pending" + case "TEAM": return "Team" + default: return "Queue" + } + } + + private buildPreview(chatItems: T.ChatItem[], customerName: string, customerId?: string): string { + const maxTotal = 500 + const maxPer = 200 + + // Collect entries in chronological order (oldest first) + const entries: {senderId: string; name: string; text: string}[] = [] + for (const ci of chatItems) { + if (ci.chatDir.type === "groupSnd") continue + + let text = (util.ciContentText(ci)?.trim() || "").replace(/\n+/g, " ") + const mediaLabel = contentTypeLabel(ci) + if (mediaLabel && !text) text = mediaLabel + else if (mediaLabel) text = `${mediaLabel} ${text}` + if (!text) continue + + let senderId = "" + let name = "" + if (ci.chatDir.type === "groupRcv") { + const member = ci.chatDir.groupMember + const contactId = member.memberContactId + senderId = member.memberId + if (this.config.grokContactId !== null && contactId === this.config.grokContactId) { + name = "Grok" + } else if (customerId && member.memberId === customerId) { + name = customerName + } else { + name = member.memberProfile.displayName + } + } + + entries.push({senderId, name, text: truncateMsg(text, maxPer)}) + } + + // Compute prefixed lines in chronological order (sender prefix on first msg of each run) + const lines: {line: string; senderId: string; name: string}[] = [] + let lastSenderId = "" + for (const entry of entries) { + let line = entry.text + if (entry.senderId !== lastSenderId && entry.name) { + line = `${entry.name}: ${line}` + lastSenderId = entry.senderId + } + lines.push({line, senderId: entry.senderId, name: entry.name}) + } + + // Take from the end (newest) until maxTotal exceeded — oldest messages are truncated + const selected: string[] = [] + let totalLen = 0 + let firstSelectedIdx = lines.length + for (let i = lines.length - 1; i >= 0; i--) { + if (totalLen + lines[i].line.length > maxTotal && selected.length > 0) { + break + } + selected.push(lines[i].line) + totalLen += lines[i].line.length + firstSelectedIdx = i + } + selected.reverse() + + // If truncation happened, ensure the first visible message has a sender prefix + if (firstSelectedIdx > 0 && selected.length > 0) { + const first = lines[firstSelectedIdx] + if (first.name && !selected[0].startsWith(`${first.name}: `)) { + selected[0] = `${first.name}: ${selected[0]}` + } + selected.unshift("[truncated]") + } + + const preview = selected.map(escapeStyledMarkdown).join(" !3 /! ") + return preview ? `"${preview}"` : '""' + } + + private formatDuration(ms: number): string { + if (ms < 60_000) return "<1m" + if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m` + if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h` + return `${Math.floor(ms / 86_400_000)}d` + } +} diff --git a/apps/simplex-support-bot/src/config.ts b/apps/simplex-support-bot/src/config.ts new file mode 100644 index 0000000000..8fbd006aef --- /dev/null +++ b/apps/simplex-support-bot/src/config.ts @@ -0,0 +1,144 @@ +import {Command} from "commander" +import {api} from "simplex-chat" + +export interface IdName { + id: number + name: string +} + +export type Backend = "sqlite" | "postgres" + +export interface Config { + stateFile: string // local path to the bot's state JSON + db: api.DbConfig // passed to ChatApi.init / bot.run + teamGroup: IdName // name from CLI, id resolved at startup from state file + teamMembers: IdName[] // optional, empty if not provided + grokContactId: number | null // resolved at startup + timezone: string + completeHours: number + cardFlushSeconds: number + contextFile: string | null + grokApiKey: string | null +} + +// Mirrors packages/simplex-chat-nodejs/src/download-libs.js so runtime detection +// matches what was used at install time. Works whether the user installed via +// SIMPLEX_BACKEND env var, .npmrc (→ npm_config_simplex_backend), or the +// --simplex_backend=postgres CLI flag (also surfaced as npm_config_*). +export function detectBackend(): Backend { + const raw = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || "sqlite").toLowerCase() + if (raw !== "sqlite" && raw !== "postgres") { + throw new Error(`Invalid SIMPLEX_BACKEND: "${raw}". Must be "sqlite" or "postgres".`) + } + return raw +} + +export function parseIdName(s: string): IdName { + const i = s.indexOf(":") + if (i < 1) throw new Error(`Invalid ID:name format: "${s}"`) + const id = parseInt(s.slice(0, i), 10) + if (isNaN(id)) throw new Error(`Invalid ID:name format (non-numeric ID): "${s}"`) + return {id, name: s.slice(i + 1)} +} + +function parseNonNegativeInt(flag: string) { + return (raw: string): number => { + const n = parseInt(raw, 10) + if (!Number.isFinite(n) || n < 0) { + throw new Error(`${flag} must be a non-negative integer, got "${raw}"`) + } + return n + } +} + +function buildCommand(): Command { + return new Command() + .name("simplex-chat-support-bot") + .description("business-address triage bot") + .requiredOption("--team-group ", "team group display name") + .option("--state-file ", "state JSON path", "./data/state.json") + .option("--sqlite-file-prefix ", "SQLite DB file prefix", "./data/simplex") + .option("--sqlite-key ", "SQLCipher encryption key (default: unencrypted)") + .option("--pg-conn ", "PostgreSQL connection string (required for postgres)") + .option("--pg-schema ", "PostgreSQL schema prefix (default: simplex_v1)") + .option("-a, --auto-add-team-members ", "comma-separated ID:name pairs (e.g. 1:Alice,2:Bob)") + .option("--timezone ", "IANA timezone for weekend detection", "UTC") + .option("--complete-hours ", "auto-complete chats after N hours idle (0 disables)", parseNonNegativeInt("--complete-hours"), 3) + .option("--card-flush-seconds ", "debounce card state writes", parseNonNegativeInt("--card-flush-seconds"), 300) + .option("--context-file ", "text file with Grok system context (required if GROK_API_KEY set)") + .addHelpText("after", "\nEnvironment:\n GROK_API_KEY xAI API key — enables Grok replies\n SIMPLEX_BACKEND sqlite | postgres — alternative to .npmrc for backend selection\n") +} + +interface RawOpts { + teamGroup: string + stateFile: string + sqliteFilePrefix: string + sqliteKey?: string + pgConn?: string + pgSchema?: string + autoAddTeamMembers?: string + timezone: string + completeHours: number + cardFlushSeconds: number + contextFile?: string +} + +export function parseConfig(args: string[]): Config { + const cmd = buildCommand().exitOverride() + try { + cmd.parse(args, {from: "user"}) + } catch (err) { + const code = (err as {code?: string}).code + if (code === "commander.helpDisplayed" || code === "commander.version") process.exit(0) + throw err + } + const opts = cmd.opts() + + const grokApiKey = process.env.GROK_API_KEY || null + + const backend = detectBackend() + let db: api.DbConfig + if (backend === "sqlite") { + db = opts.sqliteKey + ? {type: "sqlite", filePrefix: opts.sqliteFilePrefix, encryptionKey: opts.sqliteKey} + : {type: "sqlite", filePrefix: opts.sqliteFilePrefix} + } else { + if (!opts.pgConn) { + throw new Error("--pg-conn is required when backend is postgres (PostgreSQL connection string)") + } + db = opts.pgSchema + ? {type: "postgres", connectionString: opts.pgConn, schemaPrefix: opts.pgSchema} + : {type: "postgres", connectionString: opts.pgConn} + } + + const teamGroup: IdName = {id: 0, name: opts.teamGroup} + + const teamMembersRaw = opts.autoAddTeamMembers ?? "" + const teamMembers = teamMembersRaw + ? teamMembersRaw.split(",").map(parseIdName) + : [] + + try { + new Intl.DateTimeFormat("en-US", {timeZone: opts.timezone, weekday: "short"}) + } catch (err) { + throw new Error(`--timezone "${opts.timezone}" is not a valid IANA time zone: ${(err as Error).message}`) + } + + const contextFile = opts.contextFile ?? null + if (grokApiKey && !contextFile) { + throw new Error("GROK_API_KEY is set but --context-file is not provided. Grok requires a context file.") + } + + return { + stateFile: opts.stateFile, + db, + teamGroup, + teamMembers, + grokContactId: null, + timezone: opts.timezone, + completeHours: opts.completeHours, + cardFlushSeconds: opts.cardFlushSeconds, + contextFile, + grokApiKey, + } +} diff --git a/apps/simplex-support-bot/src/grok.ts b/apps/simplex-support-bot/src/grok.ts new file mode 100644 index 0000000000..b03108439a --- /dev/null +++ b/apps/simplex-support-bot/src/grok.ts @@ -0,0 +1,55 @@ +import {log, logError} from "./util.js" + +export interface GrokMessage { + role: "system" | "user" | "assistant" + content: string +} + +export class GrokApiClient { + private readonly apiKey: string + private readonly systemPrompt: string + + constructor(apiKey: string, systemPrompt: string) { + this.apiKey = apiKey + this.systemPrompt = systemPrompt + } + + async chatRaw(messages: GrokMessage[]): Promise { + const response = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${this.apiKey}`, + }, + body: JSON.stringify({ + model: "grok-3-mini", + messages, + temperature: 0.3, + max_tokens: 1024, + }), + signal: AbortSignal.timeout(60_000), + }) + + if (!response.ok) { + const body = await response.text() + logError(`Grok API HTTP ${response.status}`, body) + throw new Error(`Grok API error: HTTP ${response.status}`) + } + + const data = await response.json() as {choices: {message: {content: string}}[]} + const content = data.choices?.[0]?.message?.content + if (!content) throw new Error("Grok API returned empty response") + + log(`Grok API response: ${content.length} chars`) + return content + } + + async chat(history: GrokMessage[], userMessage: string): Promise { + log(`Grok API call: ${history.length} history msgs, user msg ${userMessage.length} chars`) + return this.chatRaw([ + {role: "system", content: this.systemPrompt}, + ...history, + {role: "user", content: userMessage}, + ]) + } +} diff --git a/apps/simplex-support-bot/src/index.ts b/apps/simplex-support-bot/src/index.ts new file mode 100644 index 0000000000..cc1dd0538c --- /dev/null +++ b/apps/simplex-support-bot/src/index.ts @@ -0,0 +1,372 @@ +import {readFileSync, writeFileSync, existsSync} from "fs" +import {api, bot, util} from "simplex-chat" +import {T} from "@simplex-chat/types" +import {parseConfig} from "./config.js" +import {SupportBot} from "./bot.js" +import {GrokApiClient} from "./grok.js" +import {welcomeMessage} from "./messages.js" +import {profileMutex, log, logError} from "./util.js" + +interface BotState { + teamGroupId?: number + grokContactId?: number + grokUserId?: number +} + +function readState(path: string): BotState { + if (!existsSync(path)) return {} + try { return JSON.parse(readFileSync(path, "utf-8")) } catch { return {} } +} + +function writeState(path: string, state: BotState): void { + writeFileSync(path, JSON.stringify(state), "utf-8") +} + +async function main(): Promise { + const config = parseConfig(process.argv.slice(2)) + // Do not log config.db.connectionString — typically contains credentials. + log("Config parsed", { + stateFile: config.stateFile, + backend: config.db.type, + teamGroup: config.teamGroup, + teamMembers: config.teamMembers, + timezone: config.timezone, + completeHours: config.completeHours, + }) + const grokEnabled = config.grokApiKey !== null + if (!grokEnabled) log("No GROK_API_KEY provided, disabling Grok support") + + const stateFilePath = config.stateFile + const state = readState(stateFilePath) + + // Forward-reference for event handlers during init + let supportBot: SupportBot | undefined + + // On restart, the active user may be Grok (if the previous run was killed + // mid-profile-switch). bot.run() uses apiGetActiveUser() and would then + // operate against the Grok userId as if it were the main user. Restore + // the main user as active before bot.run(). Grok is identified by the + // userId persisted in state.json on first resolution — comparing by + // profile name is fragile to renames. + if (state.grokUserId !== undefined) { + const preChat = await api.ChatApi.init(config.db) + try { + const activeUser = await preChat.apiGetActiveUser() + if (activeUser && activeUser.userId === state.grokUserId) { + const users = await preChat.apiListUsers() + const mainCandidates = users.filter(u => u.user.userId !== state.grokUserId) + if (mainCandidates.length === 0) { + throw new Error( + `DB has only the Grok user (userId=${state.grokUserId}); no main user to restore. ` + + `Likely a corrupted migration or partial restore.` + ) + } + if (mainCandidates.length > 1) { + const names = mainCandidates.map(u => `${u.user.userId}:${u.user.profile.displayName}`).join(", ") + throw new Error( + `Ambiguous DB state: multiple non-Grok users [${names}]. ` + + `Refusing to guess which is main — remove extras manually.` + ) + } + const mainUserInfo = mainCandidates[0] + await preChat.apiSetActiveUser(mainUserInfo.user.userId) + log(`Restored active user to ${mainUserInfo.user.profile.displayName} (userId=${mainUserInfo.user.userId})`) + } + } finally { + await preChat.close() + } + } + + // Profile images (base64-encoded JPEG) + const supportImage = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACAAIADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD6pooooAKKKKACiignAyelABRQCGAIIIPIIooAKKKKACikjdZEDxsGU8gqcg0tAk01dBRRRQMKKKKACiiigAooooAK898ZeKftBew058Qj5ZZR/H7D29+9ehVxHjTwt5++/wBMT9996WFR9/8A2h7+3f69e/LnRVZe1+Xa587xNTxtTBNYP/t627Xl+vVr8c/wf4oNkyWWoPm1PCSH/ln7H/Z/lXo6kMAVIIPIIrwTdiuw8GeKjYsljqDk2h4SQ/8ALP2P+z/KvSzDLua9WkteqPmOGeJHQtg8Y/d+zLt5Py7Pp6bel1wXjHxRv32GmyfJ92WZT97/AGV9vU1H4z8ViTfYaZJ+7+7LMp+9/sqfT1NcOGqMvy61qtVeiNeJuJea+Dwb02lJfkv1Z1PhTxI+lSiC5JeyY8jqYz6j29RXp6MHRWU5VhkGuG8F+F8eXqGpx8/eihYdP9ph/IV3VcWZTpSq/u9+p7fCdDG0cHbFP3X8Ke6X+XZdAooorzj6kKKKKACiikYhVJYgAckmgBTxRXzJ8dPi6dUNx4d8LXGNPGY7u8jP+v8AVEP9z1P8XQcddL4E/F7/AI9/Dfiu49I7K+kbr2Ech/QN+B7Gu95dWVH2tvl1scqxdN1OQ+iaKKK4DqOG8b+FPPEmoaYn7770sKj7/wDtD39u/wBevnAas346/F77X9o8N+FLj/R+Y7y+jb/WdjHGf7vYt36DjJPnvgPxibXy9M1aT/R+FhnY/wCr9FY/3fQ9vp0+ty32qpJVvl3sfnPEmS051HiMItftJfmv1PVN1eheCPCvEeo6mmScNDC36M39BXm+6u18EeLTYMljqTk2h4jkP/LL2P8As/yrTMIVnRfsfn3t5Hh8PPB08ZF4xadOyfn/AF6nqNFIrBlDKQQeQR3pa+OP2IKRHV1DIwZT0IORXn/jjxdt8zTtLk+b7s0ynp6qp/maxPB3il9HmFvdFnsHPI6mM+o9vUV6cMqrTo+169F5HzNfinCUcYsM9Y7OXRP/AC7voeuUU2KRZY0kjIZGAZSO4NOrzD6VO+qCkZQylWAKkYIPelooGfMHxz+EZ0Zp/EPheAnTDl7q0jH/AB7eroP7nqP4fp08Lr9EmUMpVgCDwQa+Yfjn8Im0dp/EPhe3LaaSXurOMZNue7oP7nqP4fp09/L8w5rUqr16M8vF4S3vwNb4FfF7/j38N+K7jniOyvpG69hHIT+QY/Q9jVb47fF03RufDfhS4xbjMd7exn/WdjHGf7vYt36DjJPz/RXZ/Z9H23tbfLpfuc/1up7PkE6D0FfRnwK+EOw2/iTxXb/PxJZ2Mi/d7iSQevcL26nnAB8C/hD5Zt/Efiy3xJxJZ2Mq/d7iSQHv3C9up5wB9D1wZhmG9Kk/VnVhMJ9uZwPjvwj9o8zUtKj/AH33poVH3/8AaX39R3+vXzLdX0XXn3j3wd9o8zUtJj/f/emgUff/ANpR6+o7/XrpleZ2tRrPTo/0Z8xxFw5z3xeEWvVd/NfqjL8DeLzp7JYam5NmTiOQ/wDLL2P+z/KtDx14xAD6dpEuT0mnQ9P9lT/M15nu5pd1etLLKMq3tmvl0v3Pm4Z9jIYP6mpad+qXYn3V6D4E8ImXy9S1WP8Ad/ehgYfe9GYenoKj8A+EPOEWp6tH+74aCBh970Zh6eg716ZXl5nmVr0aL9X+iPe4d4cvbF4tecY/q/0QUUUV86ffhRRRQAV82/HX4vfa/tHhvwpcf6NzHeX0bf6zsY4z/d7Fu/QcZJPjr8XvtRuPDfhS4/0fmO8vo2/1nYxxkfw9i3foOMk/P/8AKvdy/L7Wq1V6I8zF4v7EBOn0pa+i/gX8INot/Efiy2+fiSzsZV+76SSA9/RT06nnAGP8dPhGdHa48Q+F4CdMJL3Vogybc93Qf3PUfw/Tp3rH0XV9lf59L9jleFqKn7Q1vgV8Xjm38N+LLnJ4js76VuvYRyE/kGP0PY19E1+dlfRXwJ+L3Nv4b8V3HPEdlfSN17COQn8g34Hsa8/MMv3q0l6o68Ji/sTPomvNfiB412mTS9Hl+blZ7hT09VU+vqaj+InjfYZdK0eX5uVnuFPT1VT6+p/CvMN1dOVZTe1euvRfqz5riDP98LhX6v8ARfqybdS7q9E+HngszeVqmsRfu+Ggt2H3vRmHp6DvVz4heC/tAk1PR4v3/wB6aBR9/wD2lHr6jv8AXr6TzTDqv7C/z6X7Hgx4dxcsJ9aS/wC3etu//AMrwD4zOnMmn6pITZE4jlY5MXsf9n+X0r1pWDKGUgqRkEd6+Zd2K7z4f+NDprR6dqrk2JOI5T/yx9j/ALP8vpXFmuU8961Ba9V3815/mevw/n7o2wuKfu9H28n5fl6bev0UisGUMpBUjII70tfKn3wVHdQRXVtLb3CCSGVCjoejKRgg/hUlFAHx98Z/hbceCrttQ0tXm8PTNhWPLWrHojn09G/A89e7+BXwh8v7P4k8V2/z8SWdjIv3e4kkB79wvbqecAfQc0Mc8TRzRpJG3VXUEH8DT69GeZVZ0vZ9e5yRwcI1Of8AAKRlDKVYAg8EGlorzjrPmD45/CM6O0/iHwvATphJe6tIx/x7+roP7nqP4fp04Hwh4aB2X+pR8feihYdf9ph/IV9EfErx2B52kaLKCeUuLhT09UU/zP4V5Tur7jKaFaVFTxHy728z4LPcxgpujhX6v9F+pPur074c+CDN5Wq6zF+64aC3cfe9GYenoO9eV7q9d+G/joXXlaVrUv8ApHCwXDH/AFnorH+96Hv9eumb/WI4duh8+9vI87IaeFeKX1n5dr+f6HptFFFfBn6ceb/ETwT9pEuqaNH/AKR96eBR/rPVlH971Hf69c34d+CTdmPU9ZiIth80MDj/AFn+0w/u+g7/AE6+tUV6kc2rxw/sE/n1t2PEnkGEnivrTXy6X7/8AAAAABgCiiivLPbCiiigAooooAK8n+Jnj7YZdI0OX5uUuLlD09UU+vqfwFerSossbxuMowKkeoNeBfETwTL4cuDd2QaTSpG4PUwk/wALe3ofwPPX2sjpYepiLVnr0XRv+uh4Wf1cTTw37hadX1S/rdnG7q9U+GngPzxFq2uRfueGt7Zx9/0dh6eg79TTPhj4B87ytY1yL91w9vbOPv8Ao7D09B36mvYK9POc4tfD4d+r/RHlZJkV7YnEr0X6v/I8U+JPgZtKaTVNIjLaeTuliXkwH1H+z/L6V52GxX1c6q6lWAKkYIIyDXiXxL8CNpLSapo8ZbTyd0sK9YPcf7P8vpV5PnHtLYfEPXo+/k/P8/XfLO8i9nfE4ZadV2815fl6bb/w18eC68rSdbl/0j7sFw5/1norH+96Hv8AXr6fXjXwy8Bm9MWr61ERajDQW7D/AFvozD+76Dv9OvsteLnMcPHENYf59r+R72RyxMsMnifl3t5/oFFFFeSeyFFFFABRRRQAUUUUAFMmijmjaOZFkjYYZXGQR7in0UJ2Bq+4UUUUAFIyh1KsAVIwQRwaWigAAAAAGAKKKKACiiigAooooA//2Q==" + const grokImage = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gKgSUNDX1BST0ZJTEUAAQEAAAKQbGNtcwQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwQVBQTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWxjbXMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAADhjcHJ0AAABQAAAAE53dHB0AAABkAAAABRjaGFkAAABpAAAACxyWFlaAAAB0AAAABRiWFlaAAAB5AAAABRnWFlaAAAB+AAAABRyVFJDAAACDAAAACBnVFJDAAACLAAAACBiVFJDAAACTAAAACBjaHJtAAACbAAAACRtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABwAAAAcAHMAUgBHAEIAIABiAHUAaQBsAHQALQBpAG4AAG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAMgAAABwATgBvACAAYwBvAHAAeQByAGkAZwBoAHQALAAgAHUAcwBlACAAZgByAGUAZQBsAHkAAAAAWFlaIAAAAAAAAPbWAAEAAAAA0y1zZjMyAAAAAAABDEoAAAXj///zKgAAB5sAAP2H///7ov///aMAAAPYAADAlFhZWiAAAAAAAABvlAAAOO4AAAOQWFlaIAAAAAAAACSdAAAPgwAAtr5YWVogAAAAAAAAYqUAALeQAAAY3nBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbcGFyYQAAAAAAAwAAAAJmZgAA8qcAAA1ZAAAT0AAACltwYXJhAAAAAAADAAAAAmZmAADypwAADVkAABPQAAAKW2Nocm0AAAAAAAMAAAAAo9cAAFR7AABMzQAAmZoAACZmAAAPXP/bAEMABQMEBAQDBQQEBAUFBQYHDAgHBwcHDwsLCQwRDxISEQ8RERMWHBcTFBoVEREYIRgaHR0fHx8TFyIkIh4kHB4fHv/bAEMBBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHv/AABEIAIAAgAMBIgACEQEDEQH/xAAdAAAABgMBAAAAAAAAAAAAAAAAAQQHCAkCAwUG/8QAOxAAAQIEBAQDBgUCBgMAAAAAAQIDAAQFEQYHITEIEkFRE2FxFCJCgZGhFTJSgrEJIzNDYnKS4VOy8P/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/EABsRAQEBAAMBAQAAAAAAAAAAAAABEQISIRMx/9oADAMBAAIRAxEAPwCINoAv3gCD3BjTIhpprA3gdTB2F9NoAE9oGm194IWMAQB/WB3gD7QNIAbwBtvA2PnBjtAF08oHe/ygDUwfTf6QA6wRsNYMX84NKSYDEHXYQY1jc5Kvty7Uw42pDTpUG1H4+U2JHcA6X76RpSn7bwBQB1gesADXvAD0gAaX0gDY2gWgCEGBcHSM2m1LNrR7LLrLvE+NqsKbhukTE++LFwpHK20D8S1n3Uj1OvS8B45DK1dI3JknSNEk/KJoZfcH0gyy3MY1xA666RdUpTEhKU+RdWCT8kiHbpHD3lHTmUtpwhLTShu5NvOPKPrdVvtE1cVrGReA/IfpGlcstF7pMWeTOReUswjkXgWkpHdtKmz9UkGPBYx4Tcv6o04uhTdSoUwfyAOe0Mj9q/e+ihDTFfikLSSCLQBa58+gh684uH7G+X8u7UH5NFVpCNTPyIKktju4gjmR66p84Z1EupblgmKjUy2pZ01hy8s8u2qjQ6hjjFSn5HBtII9odQeV2fe+GVl7/Go2BVskEnfb1vDVkXPZj1NNRqQeksMyrlpmYAsqYUN2mj3/AFK+H1hfxfYzkZrEUtl7hppqTwzhYezty7As2qZtZZ035fyi/XnPWIpjsW1h+t1l6oOsMSyFAIYlWE2almU6IabHRKRp3JuTckmOMBvGx1dyTbaMQSTfYHvFRqG0DWDtpA9IAvWM2Wys2jG149blphSoYuxbTcPUxF5uffS0gkXCBupZ8kpBUfIQHvuHHJepZmVwqWpySoUmoe2zoTrffwm76FZHySNT0BsBwVhSg4PoLFEw9TmZGTZGiUD3lq6qWrdSj1J1jXl9hSk4KwlIYcozIblZRvl5iPedWdVOK7qUbk/9Q2PExnhL5cyP4LRCzM4lmW+YBfvIk0G9nFjqo/Cn5nTfLRyscY6wngqRE3iauSlOSr/DQtV3HP8AagXUr5CGSxDxd4QlHy3RsPVapJH+Y84iXSfQe8r6gRC7FmKarXqs/U6tPzE9Ovqu4++sqWryv0HkNB0jgLmnCd4uJqbklxi0lbwTN4Km22ydVM1BCyB6FI/mPZJ4psrjQXKh49VTNIICZBUmfGWSOir8lvMqiu9Mw4NeYwol3XlmwJ1hhqRuanE7jTFCXqfh0Iw3TnAUnwD4kytJ6KcIsn9oHqY5PDdkfPZjVb8TqSXZTDUs5aZmBoqYUN2mj3/Ur4fWC4ackqjmNVBUakHpTDMq5aYmALKmFDdpo9/1K+H1ifFEpdPotJlqVSpRmTkpVsNsMtJslCR0H/2u8BwcUTNMy+yvqU1S5RiSkqLTXFyzDSLITyJPIkDzVb1vFXdemnpibdefcLjziytxZ3Uom5J9STFknFEVjIPFhRv7IkfLxUX+0Vp1TV5R13hAh6awBB2Fu5gCxiow+UEN9TB9ILQ9TAZsJ5nABEwv6fuEW3alXMYzDXMZRCZGVKhstfvOEefKED9xiIMgLvA+cWJ8E1PTJZEST4A5p2dmX1H0X4Y+yBEWHQzAxJKYQwVVsSzo5mafLKd5P1q2Sj9yiB84q+x7iSpYhxDP1mqTBfnZ19Tzyyd1HoOwAsAOgAibXHfWVyOVUhS21lJqNSQF2P5kNoUu3/Ll+kQEnlFTh1trCBOpRUreCFzA0vGxlsrVFRlLsqcVoIffhmyOn8xqt+IVAOyeGZRwCZmQLKfUP8lo9+6tkjzsI0cNGSU9mPVfxGoeJI4YknLTc2PdU8oalponrb8ytkjzsIezOLiFw7gajpwRlSxJKXJN+zicaSFS0oBpytDZ1d/iPu31PMbxFOxmJmPgXJvDMrSkNMpeaZCJCjydgvkGxP6E33Urc33MYZC5wyGaDFRaFONMqEhyKWx43ihbargLSbA6EWII007xXRX8QVGr1R+oVCcfnJyYWVvPvLKlrV3JO8PfwRVp6TzrkZXnIbqMpMSyx3sjxB92/vATMzmpBruVGKKUnVb9Lf8ADFt1hBUn7gRVxVB/cJI31EW5OJSttSFpCkqFiD1BiqPH0imn4lqkgkWTLTjzIHklxSR/EIPNA2Gt4I6bWN/tAG+kFtFQUA2gDaAOusAokD/eGoixrgym0TOQdJbQdZaYmWVeR8ZSv4UIrhl1crmneJt/0/sToeolfwk64A4y8ifl090qAQ59ClH/ACiLHT4/pB17L+gVBAJblqkptZ7c7Srf+kQSmxZ0xaTnrg046yurGH2kpM4toPSZV0fbPMj625f3RWPXZF6Vm3WnmlNOtqKVoULFKgbEEdCDpCDlNpufKHSyay6k68y9inGFSFBwTTnOWcn1aLmXN/ZpcbrcPWwPKPOwhvaEKaidS9Vg+5Kt+8phhXKt/sgK1CAeqrEgbAm0dfF+MKriVyWTOKal5GRb8Gn0+VSUSsk3+ltF9L7lRupR1USYqHSzgz2ma7R0YMwRJHDGC5VsMMybJ5XphA/8pB0B35AdSTzFRhjn5lTht0jUtalExjy7xFBIJVoYf3gqkXZvPOjLSklEozMPuEdAGlJH3WIYiTaUt0C0TZ4CsDOyNKqmN5xko9sAkpEkfmbSq7ix5FQSn9pgRKTpFVGZs0icxlW5ps3S/UZhxNuxdUR/MWW5vYkawllnX6+4vlXKyS/B11Lqhytj5qUmKtas4Vum6iT1N94Qc/vbeBt5QQMH6jSKgbwAL6wYFxeM0pBMASLhUPtwc1GYkM8aCGlK5ZrxZZ1I+JCmlHX5pSflDIMNErAF4k7wMYOfqWYLuKHGiJKisKCVkaKmHU8qU/JJUfp3gqb3w6xBHjepuCmMw/aMPTgNbfuqsSrSQWm19FlXRxXxJ9CbE6uxxH8QjVITM4VwLNpcnxducqbZBTL9Cho7FfdWyelztFGh0Ku4vrPsVIp07VZ51XMpDKC4o3OqlHprupR+cJDXiCyq5sD6wXgq1uDEucAcI9TnZMzOMK0ikrWj+3KyaA84k9OdR93Tsm/qIS17hExQw6s0au0ifav7vjhbC/mLKH3i+CKCWVfpjexKLWqwB17RJaQ4T8fuupS/MUKWRfVaptavsEQ6OX3ClhulvtzeLKq7WlpIPsrCCwxfso3K1D5pieCPPD3kvWMw662stuytCl3B7bPFNhbq23+pZ+idz0BsHodLkKJRpSk0yWblZKTZSyw0gWCEJFgP++sZ0qnSFJpzMhTZRiTlGEcjTLKAhCE9gBoIZ7iPzukMB0t+iUN9qaxM8jlABCkyQI/Ov/V+lHzOm8/Q1HHPmS1NzjGAKZMBbMksTFSUg3BeseRr9oJUfMjtEQ5lXOsnXeOxXJ5+em3pmYeceedWVuOOKupaibkk9STrHHKd4uDRbvpBRmU77aQQGveCM0J1jey2VaRggXjuYWos3Wqo1T5INBxV1LceXyNMtpF1OOKOiUJGpJ+5sDYOvlpgyr4zxLL0SjtJLzl1uvOaNS7Q/M64rolI+uw1MPLmDmnS8K4MRlflXNOIpTAUmpVpPuu1B0/4hQRsknQq6iwHui5b+v4skKVhp3BWCFuJpT1jVKmpBQ/WHB3G7cuPhb3O6tTYeHJKusWQ0TzqnDoIczJHN3E2W77jNMUxN0uYc55iQmE+4s7cyVD3kqt11HcGG0Si+to2oBTtGsY1PfA3EVl/X2EIqU07QJsgczc6Lt38nU6W9eWHRpldotTbDlNq8hOIOymJlDgP0MVgMzDiNlfO8KWZ9aNUmx7jQxn5tTktAfmpZhBW/MMtJHVawkfePG4rzay+w02v8QxPIuPJF/AlV+O6fLlRe3ztFerlTfcFlrUr/cSf5hM5NrUCL2HlDod0jc2OJ2q1Jl6m4LlnKRLKukzrpBmVD/SBdLfrqfSIzVWcenHnHnnVuuOKKlrWoqUpROpJOpJ7mMnFKXurbaNC081o1OOJ2c5xF7nWE6kWBjprb3hM63oTa0TFlc1aI1KBF4WuotftCVabEiM2K2N6bWjqSc0+1KuyrbikNPlJdSk25+U3APcA622vr0Ecxu28KmCBbpFgXNnmNjG9sX1hKyrvClCtI3GK3oAAt1gwBGKVXg0nS14qMgB1+0HbcQQMAGwMAYAtqNYHKTrGN/rBBWkAZA20jEp3tB30OojFStb94DW4nS94SuiFKyLbwldVeIsJHQLGEi0+kKnlXuN4SOG+/wBoxW4//9k=" + + const desiredCommands: T.ChatBotCommand[] = [ + ...(grokEnabled ? [{type: "command" as const, keyword: "grok", label: "Ask Grok"}] : []), + {type: "command", keyword: "team", label: "Switch to team"}, + ] + + // Step 1: Init main bot via bot.run() + log("Initializing main bot...") + const [chat, mainUser, mainAddress] = await bot.run({ + profile: {displayName: "Ask SimpleX Team", fullName: "", image: supportImage}, + dbOpts: config.db, + options: { + addressSettings: { + businessAddress: true, + autoAccept: true, + welcomeMessage, + }, + commands: desiredCommands, + useBotProfile: true, + updateProfile: false, + }, + events: { + acceptingBusinessRequest: (evt) => supportBot?.onBusinessRequest(evt), + newChatItems: (evt) => supportBot?.onNewChatItems(evt), + chatItemUpdated: (evt) => supportBot?.onChatItemUpdated(evt), + chatItemReaction: (evt) => supportBot?.onChatItemReaction(evt), + leftMember: (evt) => supportBot?.onLeftMember(evt), + joinedGroupMember: (evt) => supportBot?.onJoinedGroupMember(evt), + connectedToGroupMember: (evt) => supportBot?.onMemberConnected(evt), + newMemberContactReceivedInv: (evt) => supportBot?.onMemberContactReceivedInv(evt), + contactConnected: (evt) => supportBot?.onContactConnected(evt), + contactSndReady: (evt) => supportBot?.onContactSndReady(evt), + }, + }) + log(`Main bot user: ${mainUser.profile.displayName} (userId=${mainUser.userId})`) + + // Step 2: Resolve Grok profile from same ChatApi instance. + // Identify Grok strictly by the persisted userId in state.json. If no ID + // is persisted, this is a first-time run — create the user and persist. + let grokUser: T.User | null = null + if (grokEnabled) { + log("Resolving Grok profile...") + if (state.grokUserId !== undefined) { + const users = await chat.apiListUsers() + grokUser = users.find(u => u.user.userId === state.grokUserId)?.user ?? null + if (!grokUser) { + throw new Error( + `Persisted Grok userId=${state.grokUserId} not found in DB. ` + + `Either restore the user or delete state.json to re-create Grok.` + ) + } + } else { + log("Creating Grok profile...") + grokUser = await chat.apiCreateActiveUser({displayName: "Grok", fullName: "", image: grokImage}) + // apiCreateActiveUser sets Grok as active — switch back to main + await chat.apiSetActiveUser(mainUser.userId) + state.grokUserId = grokUser.userId + writeState(stateFilePath, state) + log(`Persisted Grok userId=${grokUser.userId}`) + } + + // Refresh Grok's profile if it has drifted from the canonical values. + const grokProfile: T.Profile = {displayName: "Grok", fullName: "", image: grokImage} + const currentProfile = util.fromLocalProfile(grokUser.profile) + if (currentProfile.image !== grokProfile.image || currentProfile.displayName !== grokProfile.displayName || currentProfile.fullName !== grokProfile.fullName) { + log("Grok profile changed, updating...") + await chat.apiSetActiveUser(grokUser.userId) + const summary = await chat.apiUpdateProfile(grokUser.userId, grokProfile) + await chat.apiSetActiveUser(mainUser.userId) + if (summary) { + log(`Grok profile updated: ${summary.updateSuccesses} contact(s) updated, ${summary.updateFailures} failed`) + } else { + log("Unexpected: Grok profile did not change") + } + } + log(`Grok profile: ${grokUser.profile.displayName} (userId=${grokUser.userId})`) + } + + // Step 3: Read state file + // Step 4: Enable auto-accept DM contacts + await chat.apiSetAutoAcceptMemberContacts(mainUser.userId, true) + log("Auto-accept member contacts enabled") + + // Step 5: List contacts, resolve Grok contact + const contacts = await chat.apiListContacts(mainUser.userId) + log(`Contacts connected: ${contacts.length || "(none)"}`) + + // Always restore grokContactId so the one-way gate can find and remove + // Grok members even when Grok API is disabled. + if (typeof state.grokContactId === "number") { + const found = contacts.find(c => c.contactId === state.grokContactId) + if (found) { + config.grokContactId = found.contactId + log(`Grok contact from state: ID=${config.grokContactId}`) + } else { + log(`Persisted Grok contact ID=${state.grokContactId} not found`) + } + } + + if (grokEnabled) { + if (config.grokContactId === null) { + log("Establishing bot↔Grok contact...") + const invLink = await chat.apiCreateLink(mainUser.userId) + // Switch to Grok profile to connect + await profileMutex.runExclusive(async () => { + await chat.apiSetActiveUser(grokUser!.userId) + await chat.apiConnectActiveUser(invLink) + await chat.apiSetActiveUser(mainUser.userId) + }) + log("Grok connecting...") + + const grokProfileName = grokUser!.profile.displayName + const evt = await chat.wait( + "contactConnected", + (e) => + e.user.userId === mainUser.userId + && e.contact.profile.displayName === grokProfileName, + 60_000, + ) + if (!evt) { + console.error(`Timeout waiting for Grok contact (60s, displayName="${grokProfileName}"). Exiting.`) + process.exit(1) + } + config.grokContactId = evt.contact.contactId + state.grokContactId = config.grokContactId + writeState(stateFilePath, state) + log(`Grok contact established: ID=${config.grokContactId}`) + } + } + + // Step 6: Resolve team group + log("Resolving team group...") + const groups = await chat.apiListGroups(mainUser.userId) + + let existingGroup: T.GroupInfo | undefined + + if (typeof state.teamGroupId === "number") { + existingGroup = groups.find(g => g.groupId === state.teamGroupId) + if (existingGroup) { + config.teamGroup.id = existingGroup.groupId + log(`Team group from state: ${config.teamGroup.id}:${existingGroup.groupProfile.displayName}`) + } else { + log(`Persisted team group ID=${state.teamGroupId} not found, will create`) + } + } + + const teamGroupPreferences: T.GroupPreferences = { + directMessages: {enable: T.GroupFeatureEnabled.On}, + fullDelete: {enable: T.GroupFeatureEnabled.On}, + commands: [ + {type: "command", keyword: "join", label: "Join customer chat", params: "groupId"}, + ], + } + + if (config.teamGroup.id === 0) { + log(`Creating team group "${config.teamGroup.name}"...`) + const newGroup = await chat.apiNewGroup(mainUser.userId, { + displayName: config.teamGroup.name, + fullName: "", + groupPreferences: teamGroupPreferences, + }) + config.teamGroup.id = newGroup.groupId + state.teamGroupId = config.teamGroup.id + writeState(stateFilePath, state) + log(`Team group created: ${config.teamGroup.id}:${config.teamGroup.name}`) + } else if (existingGroup) { + // Only update profile if preferences or name changed + const prefs = existingGroup.fullGroupPreferences + const needsUpdate = + existingGroup.groupProfile.displayName !== config.teamGroup.name || + prefs.directMessages?.enable !== T.GroupFeatureEnabled.On || + prefs.fullDelete?.enable !== T.GroupFeatureEnabled.On || + JSON.stringify(prefs.commands) !== JSON.stringify(teamGroupPreferences.commands) + if (needsUpdate) { + await chat.apiUpdateGroupProfile(config.teamGroup.id, { + displayName: config.teamGroup.name, + fullName: "", + groupPreferences: teamGroupPreferences, + }) + log("Team group profile updated") + } + } + + // Step 7: Ensure direct messages enabled (done via groupPreferences above) + + // Step 8: Create team group invite link (best-effort — bot works without it) + let inviteLinkCreated = false + try { + try { await chat.apiDeleteGroupLink(config.teamGroup.id) } catch {} + const teamGroupInviteLink = await chat.apiCreateGroupLink( + config.teamGroup.id, T.GroupMemberRole.Member + ) + inviteLinkCreated = true + log("Team group invite link created") + console.log(`\nTeam group invite link (expires in 10 min):\n${teamGroupInviteLink}\n`) + } catch (err) { + logError("Failed to create team group invite link (SMP relay may be unreachable). Bot will continue without it.", err) + } + + let inviteLinkDeleted = false + async function deleteInviteLink(): Promise { + if (inviteLinkDeleted) return + inviteLinkDeleted = true + try { + await profileMutex.runExclusive(async () => { + await chat.apiSetActiveUser(mainUser.userId) + await chat.apiDeleteGroupLink(config.teamGroup.id) + }) + log("Team group invite link deleted") + } catch (err) { + logError("Failed to delete invite link", err) + } + } + let inviteLinkTimer: ReturnType | undefined + if (inviteLinkCreated) { + inviteLinkTimer = setTimeout(async () => { + log("10 minutes elapsed, deleting invite link...") + await deleteInviteLink() + }, 10 * 60 * 1000) + inviteLinkTimer.unref() + } + + // Step 9: Validate team members + if (config.teamMembers.length > 0) { + log("Validating team members...") + for (const member of config.teamMembers) { + const contact = contacts.find(c => c.contactId === member.id) + if (!contact) { + console.error(`Team member not found: ID=${member.id}. Available: ${contacts.map(c => `${c.contactId}:${c.profile.displayName}`).join(", ") || "(none)"}`) + process.exit(1) + } + if (contact.profile.displayName !== member.name) { + console.error(`Team member name mismatch: expected "${member.name}", got "${contact.profile.displayName}" (ID=${member.id})`) + process.exit(1) + } + log(`Team member validated: ${member.id}:${member.name}`) + } + } + + // Load Grok context and build API client only if enabled + let grokApi: GrokApiClient | null = null + if (grokEnabled) { + let contextFile = "" + if (config.contextFile) { + try { + contextFile = readFileSync(config.contextFile, "utf-8") + log(`Loaded Grok context: ${contextFile.length} chars from ${config.contextFile}`) + } catch { + log(`Warning: context file not found: ${config.contextFile}`) + } + } + grokApi = new GrokApiClient(config.grokApiKey!, contextFile) + } + + // Create SupportBot + supportBot = new SupportBot(chat, grokApi, config, mainUser.userId, grokUser?.userId ?? null, desiredCommands) + + if (mainAddress) { + supportBot.businessAddress = util.contactAddressStr(mainAddress.connLinkContact) + log(`Business address: ${supportBot.businessAddress}`) + } + + // Step 10: Register Grok event handlers (filtered by profile in handler) + if (grokEnabled) { + chat.on("receivedGroupInvitation", (evt) => supportBot?.onGrokGroupInvitation(evt)) + chat.on("connectedToGroupMember", (evt) => supportBot?.onGrokMemberConnected(evt)) + chat.on("newChatItems", (evt) => supportBot?.onGrokNewChatItems(evt)) + } + + // Step 10b: Refresh stale cards from before restart + await supportBot.cards.refreshAllCards() + + log("SupportBot initialized. Bot running.") + + // Step 11: Graceful shutdown + async function shutdown(signal: string): Promise { + log(`Received ${signal}, shutting down...`) + clearTimeout(inviteLinkTimer) + supportBot?.cards.destroy() + await deleteInviteLink() + process.exit(0) + } + process.on("SIGINT", () => shutdown("SIGINT")) + process.on("SIGTERM", () => shutdown("SIGTERM")) +} + +main().catch(err => { + logError("Fatal error", err) + process.exit(1) +}) diff --git a/apps/simplex-support-bot/src/messages.ts b/apps/simplex-support-bot/src/messages.ts new file mode 100644 index 0000000000..c35789d26b --- /dev/null +++ b/apps/simplex-support-bot/src/messages.ts @@ -0,0 +1,44 @@ +import {isWeekend} from "./util.js" + +export const welcomeMessage = `Hello! This is a *SimpleX team* support bot - not an AI. +*Join public groups* at https://simplex.chat/directory or [via directory bot](https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok) +Please ask any questions about SimpleX Chat.` + +export function queueMessage(timezone: string, grokEnabled: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `The team will reply to your message within ${hours} hours.` + if (!grokEnabled) return base + return `${base} + +If your question is about SimpleX, click /grok for an *instant Grok answer*. + +Send /team to switch back.` +} + +export const grokActivatedMessage = `*You are now chatting with Grok* - use any language.` + +export function teamAddedMessage(timezone: string, grokPresent: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `We will reply within ${hours} hours.` + if (!grokPresent) return base + return `${base} +Grok will be answering your questions until then.` +} + +export const teamAlreadyInvitedMessage = "A team member was invited to this conversation and will reply when available." + +export const teamLockedMessage = "Only the team will now receive your messages." + +export function noTeamMembersMessage(grokEnabled: boolean): string { + return grokEnabled + ? "No team members are available yet. Please try again later or click /grok." + : "No team members are available yet. Please try again later." +} + +export const grokInvitingMessage = "Inviting Grok, please wait..." + +export const grokUnavailableMessage = "Grok is temporarily unavailable. Please try again later or send /team for a human team member." + +export const grokErrorMessage = "Sorry, I couldn't process that. Please try again or send /team for a human team member." + +export const grokNoHistoryMessage = "I just joined but couldn't see your earlier messages. Could you repeat your question?" diff --git a/apps/simplex-support-bot/src/util.ts b/apps/simplex-support-bot/src/util.ts new file mode 100644 index 0000000000..288a48d673 --- /dev/null +++ b/apps/simplex-support-bot/src/util.ts @@ -0,0 +1,22 @@ +import {Mutex} from "async-mutex" + +export const profileMutex = new Mutex() + +export function isWeekend(timezone: string): boolean { + const day = new Intl.DateTimeFormat("en-US", {timeZone: timezone, weekday: "short"}).format(new Date()) + return day === "Sat" || day === "Sun" +} + +export function log(msg: string, ...args: unknown[]): void { + const ts = new Date().toISOString() + if (args.length > 0) { + console.log(`[${ts}] ${msg}`, ...args) + } else { + console.log(`[${ts}] ${msg}`) + } +} + +export function logError(msg: string, err: unknown): void { + const ts = new Date().toISOString() + console.error(`[${ts}] ERROR: ${msg}`, err) +} diff --git a/apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js b/apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js new file mode 100644 index 0000000000..29fc3d01a4 --- /dev/null +++ b/apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js @@ -0,0 +1,12 @@ +// Mock for @simplex-chat/types — lightweight stubs + +const ChatType = {Direct: "direct", Group: "group", Local: "local"} +const GroupMemberRole = {Member: "member", Owner: "owner", Admin: "admin", Relay: "relay", Observer: "observer", Author: "author", Moderator: "moderator"} +const GroupMemberStatus = {Connected: "connected", Complete: "complete", Announced: "announced", Left: "left", Removed: "removed", Invited: "invited"} +const GroupFeatureEnabled = {On: "on", Off: "off"} +const CIDeleteMode = {Broadcast: "broadcast", Internal: "internal"} + +module.exports = { + T: {ChatType, GroupMemberRole, GroupMemberStatus, GroupFeatureEnabled, CIDeleteMode}, + CEvt: {}, +} diff --git a/apps/simplex-support-bot/test/__mocks__/simplex-chat.js b/apps/simplex-support-bot/test/__mocks__/simplex-chat.js new file mode 100644 index 0000000000..64c9246f27 --- /dev/null +++ b/apps/simplex-support-bot/test/__mocks__/simplex-chat.js @@ -0,0 +1,26 @@ +// Mock for simplex-chat — prevents native addon from loading + +function ciContentText(chatItem) { + const c = chatItem.content + if (c.type === "sndMsgContent" || c.type === "rcvMsgContent") return c.msgContent.text + return undefined +} + +function ciBotCommand(chatItem) { + const text = ciContentText(chatItem)?.trim() + if (text) { + const r = text.match(/\/([^\s]+)(.*)/) + if (r && r.length >= 3) return {keyword: r[1], params: r[2].trim()} + } + return undefined +} + +function contactAddressStr(link) { + return link.connShortLink || link.connFullLink +} + +module.exports = { + api: {ChatApi: {}}, + bot: {}, + util: {ciContentText, ciBotCommand, contactAddressStr}, +} diff --git a/apps/simplex-support-bot/tsconfig.json b/apps/simplex-support-bot/tsconfig.json new file mode 100644 index 0000000000..821fa663e3 --- /dev/null +++ b/apps/simplex-support-bot/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["src"], + "compilerOptions": { + "declaration": true, + "forceConsistentCasingInFileNames": true, + "lib": ["ES2022"], + "module": "Node16", + "moduleResolution": "Node16", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noEmitOnError": true, + "outDir": "dist", + "sourceMap": true, + "strict": true, + "strictNullChecks": true, + "target": "ES2022", + "types": ["node"] + } +} diff --git a/apps/simplex-support-bot/vitest.config.ts b/apps/simplex-support-bot/vitest.config.ts new file mode 100644 index 0000000000..c143572a87 --- /dev/null +++ b/apps/simplex-support-bot/vitest.config.ts @@ -0,0 +1,22 @@ +import {defineConfig} from "vitest/config" +import path from "path" + +export default defineConfig({ + test: { + globals: true, + testTimeout: 10000, + // Clear backend signals — .npmrc next to package.json otherwise injects + // npm_config_simplex_backend into every test's env, breaking sqlite-default + // assumptions in parseConfig tests. + env: { + SIMPLEX_BACKEND: "", + npm_config_simplex_backend: "", + }, + }, + resolve: { + alias: { + "simplex-chat": path.resolve(__dirname, "test/__mocks__/simplex-chat.js"), + "@simplex-chat/types": path.resolve(__dirname, "test/__mocks__/simplex-chat-types.js"), + }, + }, +}) diff --git a/assets/ASSETS_LICENSE.md b/assets/ASSETS_LICENSE.md new file mode 100644 index 0000000000..36ac54d04f --- /dev/null +++ b/assets/ASSETS_LICENSE.md @@ -0,0 +1,18 @@ +# Application Graphic Assets License + +Copyright (C) 2026 SimpleX Chat Ltd. All rights reserved. + +The graphic assets in this folder, subfolders and in other folders of this repository - including illustrations, images, icons, and visual designs - are proprietary and are not licensed under the AGPLv3 that covers the application source code. + +## Permitted use + +- Unmodified application distribution. You may use these assets as part of the SimpleX Chat application, provided the application is not modified in any way. +- Publications with permission. You may use screenshots containing these assets in publications with prior written permission from SimpleX Chat Ltd. + +## Not permitted + +All other use, including modification, redistribution, or incorporation into other works, is not permitted without prior written permission from SimpleX Chat Ltd. + +## Contact + +To request permission, contact chat@simplex.chat. diff --git a/assets/multiplatform/resources/MR/images/banner_create_link@2x.png b/assets/multiplatform/resources/MR/images/banner_create_link@2x.png new file mode 100644 index 0000000000..6f768932ad Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_create_link@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_create_link@3x.png b/assets/multiplatform/resources/MR/images/banner_create_link@3x.png new file mode 100644 index 0000000000..8fe3b9c035 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_create_link@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_create_link_light@2x.png b/assets/multiplatform/resources/MR/images/banner_create_link_light@2x.png new file mode 100644 index 0000000000..c7bfca381a Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_create_link_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_create_link_light@3x.png b/assets/multiplatform/resources/MR/images/banner_create_link_light@3x.png new file mode 100644 index 0000000000..a25f96d596 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_create_link_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link@2x.png b/assets/multiplatform/resources/MR/images/banner_paste_link@2x.png new file mode 100644 index 0000000000..44feaf8845 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_paste_link@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link@3x.png b/assets/multiplatform/resources/MR/images/banner_paste_link@3x.png new file mode 100644 index 0000000000..4d1a6e2fda Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_paste_link@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link_light@2x.png b/assets/multiplatform/resources/MR/images/banner_paste_link_light@2x.png new file mode 100644 index 0000000000..c34e988886 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_paste_link_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link_light@3x.png b/assets/multiplatform/resources/MR/images/banner_paste_link_light@3x.png new file mode 100644 index 0000000000..9d813e2c8e Binary files /dev/null and b/assets/multiplatform/resources/MR/images/banner_paste_link_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@2x.png new file mode 100644 index 0000000000..3f53f89dd6 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@3x.png b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@3x.png new file mode 100644 index 0000000000..490afadc98 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@2x.png b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@2x.png new file mode 100644 index 0000000000..77a072dc62 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@3x.png new file mode 100644 index 0000000000..556c4d36a4 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@2x.png new file mode 100644 index 0000000000..b5c813009b Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@3x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@3x.png new file mode 100644 index 0000000000..165e84c64a Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@2x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@2x.png new file mode 100644 index 0000000000..6f133967da Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@3x.png new file mode 100644 index 0000000000..38970844b7 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@2x.png new file mode 100644 index 0000000000..3d54b2c507 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@3x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@3x.png new file mode 100644 index 0000000000..c0e92a91a2 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@2x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@2x.png new file mode 100644 index 0000000000..329fc8d6c4 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@3x.png new file mode 100644 index 0000000000..99fb7a45d6 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@2x.png new file mode 100644 index 0000000000..cc0446d16f Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@3x.png b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@3x.png new file mode 100644 index 0000000000..8ea447c884 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@2x.png b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@2x.png new file mode 100644 index 0000000000..b37a483be1 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@3x.png new file mode 100644 index 0000000000..414870fc3a Binary files /dev/null and b/assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link@2x.png b/assets/multiplatform/resources/MR/images/connect_via_link@2x.png new file mode 100644 index 0000000000..24be83e066 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link@3x.png b/assets/multiplatform/resources/MR/images/connect_via_link@3x.png new file mode 100644 index 0000000000..73f118580c Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link_light@2x.png b/assets/multiplatform/resources/MR/images/connect_via_link_light@2x.png new file mode 100644 index 0000000000..8a2d8e605a Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link_light@3x.png b/assets/multiplatform/resources/MR/images/connect_via_link_light@3x.png new file mode 100644 index 0000000000..b6ee8a4bb6 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link_small@2x.png b/assets/multiplatform/resources/MR/images/connect_via_link_small@2x.png new file mode 100644 index 0000000000..b105e3be3e Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link_small@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link_small@3x.png b/assets/multiplatform/resources/MR/images/connect_via_link_small@3x.png new file mode 100644 index 0000000000..1e410de4b5 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link_small@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link_small_light@2x.png b/assets/multiplatform/resources/MR/images/connect_via_link_small_light@2x.png new file mode 100644 index 0000000000..73520c2f68 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link_small_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/connect_via_link_small_light@3x.png b/assets/multiplatform/resources/MR/images/connect_via_link_small_light@3x.png new file mode 100644 index 0000000000..565d44690b Binary files /dev/null and b/assets/multiplatform/resources/MR/images/connect_via_link_small_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel@2x.png b/assets/multiplatform/resources/MR/images/create_channel@2x.png new file mode 100644 index 0000000000..a14e4c5e11 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel@3x.png b/assets/multiplatform/resources/MR/images/create_channel@3x.png new file mode 100644 index 0000000000..dfd6945d9c Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel_light@2x.png b/assets/multiplatform/resources/MR/images/create_channel_light@2x.png new file mode 100644 index 0000000000..5dafcad62a Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel_light@3x.png b/assets/multiplatform/resources/MR/images/create_channel_light@3x.png new file mode 100644 index 0000000000..7600906d71 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_group@2x.png b/assets/multiplatform/resources/MR/images/create_group@2x.png new file mode 100644 index 0000000000..7fa788d8c8 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_group@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_group@3x.png b/assets/multiplatform/resources/MR/images/create_group@3x.png new file mode 100644 index 0000000000..cd4bfa45a4 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_group@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_group_light@2x.png b/assets/multiplatform/resources/MR/images/create_group_light@2x.png new file mode 100644 index 0000000000..de32b94652 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_group_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_group_light@3x.png b/assets/multiplatform/resources/MR/images/create_group_light@3x.png new file mode 100644 index 0000000000..a05610cbb9 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_group_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_profile@2x.png b/assets/multiplatform/resources/MR/images/create_profile@2x.png new file mode 100644 index 0000000000..0639186b0b Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_profile@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_profile@3x.png b/assets/multiplatform/resources/MR/images/create_profile@3x.png new file mode 100644 index 0000000000..1813fea83b Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_profile@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_profile_light@2x.png b/assets/multiplatform/resources/MR/images/create_profile_light@2x.png new file mode 100644 index 0000000000..2a3f4931e9 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_profile_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_profile_light@3x.png b/assets/multiplatform/resources/MR/images/create_profile_light@3x.png new file mode 100644 index 0000000000..9a9fd22cfb Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_profile_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/intro@2x.png b/assets/multiplatform/resources/MR/images/intro@2x.png new file mode 100644 index 0000000000..970d68927c Binary files /dev/null and b/assets/multiplatform/resources/MR/images/intro@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/intro@3x.png b/assets/multiplatform/resources/MR/images/intro@3x.png new file mode 100644 index 0000000000..cbd56771c7 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/intro@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/intro_light@2x.png b/assets/multiplatform/resources/MR/images/intro_light@2x.png new file mode 100644 index 0000000000..938a0b1755 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/intro_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/intro_light@3x.png b/assets/multiplatform/resources/MR/images/intro_light@3x.png new file mode 100644 index 0000000000..569c56fd29 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/intro_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/network_commitments@2x.png b/assets/multiplatform/resources/MR/images/network_commitments@2x.png new file mode 100644 index 0000000000..4b58a588d3 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/network_commitments@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/network_commitments@3x.png b/assets/multiplatform/resources/MR/images/network_commitments@3x.png new file mode 100644 index 0000000000..9b80a623a1 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/network_commitments@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/network_commitments_light@2x.png b/assets/multiplatform/resources/MR/images/network_commitments_light@2x.png new file mode 100644 index 0000000000..5e07e0afdb Binary files /dev/null and b/assets/multiplatform/resources/MR/images/network_commitments_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/network_commitments_light@3x.png b/assets/multiplatform/resources/MR/images/network_commitments_light@3x.png new file mode 100644 index 0000000000..aadfa0288d Binary files /dev/null and b/assets/multiplatform/resources/MR/images/network_commitments_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link@2x.png b/assets/multiplatform/resources/MR/images/one_time_link@2x.png new file mode 100644 index 0000000000..8b3ba2f0ee Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link@3x.png b/assets/multiplatform/resources/MR/images/one_time_link@3x.png new file mode 100644 index 0000000000..de87789d1b Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link_light@2x.png b/assets/multiplatform/resources/MR/images/one_time_link_light@2x.png new file mode 100644 index 0000000000..3b0c02209b Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link_light@3x.png b/assets/multiplatform/resources/MR/images/one_time_link_light@3x.png new file mode 100644 index 0000000000..87360c3135 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link_small@2x.png b/assets/multiplatform/resources/MR/images/one_time_link_small@2x.png new file mode 100644 index 0000000000..f9d94cf265 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link_small@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link_small@3x.png b/assets/multiplatform/resources/MR/images/one_time_link_small@3x.png new file mode 100644 index 0000000000..2dac7ef638 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link_small@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link_small_light@2x.png b/assets/multiplatform/resources/MR/images/one_time_link_small_light@2x.png new file mode 100644 index 0000000000..916bdaa007 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link_small_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/one_time_link_small_light@3x.png b/assets/multiplatform/resources/MR/images/one_time_link_small_light@3x.png new file mode 100644 index 0000000000..1ed8194bc9 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/one_time_link_small_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address@2x.png b/assets/multiplatform/resources/MR/images/simplex_address@2x.png new file mode 100644 index 0000000000..237c125c62 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address@3x.png b/assets/multiplatform/resources/MR/images/simplex_address@3x.png new file mode 100644 index 0000000000..8f5606cbbc Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address_light@2x.png b/assets/multiplatform/resources/MR/images/simplex_address_light@2x.png new file mode 100644 index 0000000000..a58ebae39c Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address_light@3x.png b/assets/multiplatform/resources/MR/images/simplex_address_light@3x.png new file mode 100644 index 0000000000..aae91169ef Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address_small@2x.png b/assets/multiplatform/resources/MR/images/simplex_address_small@2x.png new file mode 100644 index 0000000000..6dddbbd377 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address_small@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address_small@3x.png b/assets/multiplatform/resources/MR/images/simplex_address_small@3x.png new file mode 100644 index 0000000000..45471e9c50 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address_small@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address_small_light@2x.png b/assets/multiplatform/resources/MR/images/simplex_address_small_light@2x.png new file mode 100644 index 0000000000..a1cdfc4652 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address_small_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/simplex_address_small_light@3x.png b/assets/multiplatform/resources/MR/images/simplex_address_small_light@3x.png new file mode 100644 index 0000000000..f54baf5dc4 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/simplex_address_small_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_network@2x.png b/assets/multiplatform/resources/MR/images/your_network@2x.png new file mode 100644 index 0000000000..b7b5d6aa87 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_network@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_network@3x.png b/assets/multiplatform/resources/MR/images/your_network@3x.png new file mode 100644 index 0000000000..9ff0e77a86 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_network@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_network_light@2x.png b/assets/multiplatform/resources/MR/images/your_network_light@2x.png new file mode 100644 index 0000000000..12031202d8 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_network_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_network_light@3x.png b/assets/multiplatform/resources/MR/images/your_network_light@3x.png new file mode 100644 index 0000000000..56b7f20c59 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_network_light@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_profile@2x.png b/assets/multiplatform/resources/MR/images/your_profile@2x.png new file mode 100644 index 0000000000..81e8e1a6b0 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_profile@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_profile@3x.png b/assets/multiplatform/resources/MR/images/your_profile@3x.png new file mode 100644 index 0000000000..01ea5da43c Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_profile@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_profile_light@2x.png b/assets/multiplatform/resources/MR/images/your_profile_light@2x.png new file mode 100644 index 0000000000..91671dadb0 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_profile_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/your_profile_light@3x.png b/assets/multiplatform/resources/MR/images/your_profile_light@3x.png new file mode 100644 index 0000000000..8e1d3fd15e Binary files /dev/null and b/assets/multiplatform/resources/MR/images/your_profile_light@3x.png differ diff --git a/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md b/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md deleted file mode 100644 index 39616304dd..0000000000 --- a/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: layouts/article.html -title: "SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech" -date: 2026-04-28 -# previewBody: blog_previews/20260421.html -# image: images/20260421-channel.png -# imageBottom: true -draft: true -permalink: "/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html" ---- - -# SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech - -**To be published:** Apr 28, 2026 - -This is a permalink for a blog post about: - -- SimpleX Channels - a new model for online publishing that preserves participation privacy, protecting both user and network operators. It is being released in v6.5 -- SimpleX Network Consortium - a cross-jurisdictional governance and licensing structure to ensure long term availability and sustainability of SimpleX Network. -- Testing the water for community crowdfunding under Reg CF. - -## SimpleX Channels - more public, more freedom, more private - -TODO - -## SimpleX Network Consortium - to govern SimpleX Network - -TODO - -## Community Crowdfunding - -TODO - -*Register your interest* to participate in crowdfunding here: https://simplexchat.typeform.com/crowdfunding - -Join the channel for updates here: https://smp4.simplex.im/g#g6pdBGlLoeOwqYmbmyvRye8EBiFd2inNUzKc87Pt3y4 - -_Disclaimer: SimpleX Chat is testing the waters for a possible Reg CF offering. We’re not asking for or accepting any money right now, and we won’t accept any if sent. We can’t accept any offers to buy securities or take any payments until the official filing is done and it’s live through a regulated platform. Our testing the waters and your possible indications of interest doesn’t create any obligation or commitment of any kind._ diff --git a/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md b/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md new file mode 100644 index 0000000000..64ebc03b2e --- /dev/null +++ b/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md @@ -0,0 +1,71 @@ +--- +layout: layouts/article.html +title: "SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech" +date: 2026-04-30 +previewBody: blog_previews/20260430.html +image: images/20260430-home.png +imageLight: images/20260430-home-light.png +permalink: "/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html" +--- + +# SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding — to Preserve Freedom of Speech + +**Published:** Apr 30, 2026 + +Freedom of speech needs infrastructure that protects it by design — not only the protocols and servers, but the governance and funding to support them. + +## SimpleX Channels — more public, more freedom, more private + + + +v6.5 release[^release] brings SimpleX Channels: a new model for online publishing built for participation privacy. + +Channel content is visible to chat relay operators. And each channel uses multiple relays, so no single relay can block the channel[^preset]. + +But the real identities of channel owners and subscribers are unknown to relay operators, to each other, and to the network. This is important for freedom of speech and for our ability to say the truth[^wilde]. + +This is the opposite of the usual approach: instead of trying (and failing [^public]) to hide publicly available content from operators while exposing participants, we designed the protocols to protect people. Anybody can join a public channel via its link and see what is sent, but not who sent it, and not who else is reading. This is win-win for both users and chat relays operators. Users' privacy is protected, operators can decide what content to deliver in public spaces, and anybody can run chat relays. + +This is only possible because SimpleX network was built without user profile identifiers of any kind. You can't add participation privacy to a network that identifies its users — as you can't add privacy to a messenger built on phone numbers. + +v6.5 is the first beta version of channels: +- channel owners hold their own channel keys, +- each channel uses multiple relays for reliability, +- publishers can run their own chat relays, +- channels can be added to our [SimpleX Directory](https://simplex.chat/directory/). + +This release is a beginning of a very important new layer of SimpleX Network. Read more about channels in [whitepaper](https://github.com/simplex-chat/simplex-chat/blob/master/docs/protocol/channels-overview.md): their purpose, architecture, security model and planned future work. + +## SimpleX Network Consortium — to preserve network independence + +No single company should control protocols and network that people depend on to speak freely. If a network is run by a single company, the network has a risk that business and users interests diverge — if it happens, users lose. + +To protect network neutrality and make sure its protocols and intellectual property are available to the users, we're launching [SimpleX Network Consortium](https://simplexnetwork.org) within a few months — the agreement between the new SimpleX Network Foundation and SimpleX Chat company that will govern protocols and licensing — perpetual, irrevocable, surviving if any party is sold or shut down. Other organizations will join. + +We are currently forming the board for SimpleX Network Foundation — initially, [Heather Meeker](https://heathermeeker.com/about-me/), who drafted the Consortium agreement, and several other people will join. We will announce the board soon. + +As the power over the network protocols moves away from the company, it cannot move back[^ulysses]. It is a structural guarantee — the same principle we applied to privacy. + +## Community Crowdfunding + +We've seen open-source privacy-focussed projects die without funding, or worse — being captured by their sponsors. We've seen "don't be evil" companies get lured off course by growth and board pressure. Neither pure ideology nor pure commerce survives the long run alone. + +So we're building both: a governance structure and a real business. The governance protects the network neutrality. The commercial model funds the network and makes our and other businesses on the network profitable, ensuring their independence. Neither works without the other. + +We recently published [a preliminary design of commercial model](https://simplex.chat/vouchers/) — private Community Credits that fund servers, development, and governance without surveillance or speculation. The full investment case will be published when crowdfunding launches. + +You can *register your interest* to participate in crowdfunding here: https://simplexchat.typeform.com/crowdfunding + +Join the channel for updates [here](https://smp10.simplex.im/c#q09nMBmWFGz1m2TvgfZFaEOG5D2a7Ma9mSkl6pHXEsg) — you must install v6.5 to join it — or you can join a [read-only group](https://smp12.simplex.im/g#gJzy7ETpuvltqARIB73TQUpJ11Lz4Xpl9xeH9qNoGCg) from the previous app versions. + +_Disclaimer: SimpleX Chat is testing the waters for a possible Reg CF offering. We’re not asking for or accepting any money right now, and we won’t accept any if sent. We can’t accept any offers to buy securities or take any payments until the official filing is done and it’s live through a regulated platform. Our testing the waters and your possible indications of interest doesn’t create any obligation or commitment of any kind._ + +[^release]: v6.5 release also improved how new users make the first connection, increased security of sending web links, and has many other improvements — see *What's new* in the app or full release notes. + +[^preset]: Currently there is only one preset operator of chat relays in the app. It will change in the next release. + +[^wilde]: Oscar Wilde wrote: *"Man is least himself when he talks in his own person. Give him a mask, and he will tell you the truth"*. Privacy is essential for our ability to say the truth, and without truth we cannot survive as society. + +[^public]: From whitepaper: any channel joinable via a public link, whether encrypted or not, must be considered completely public — the cost of joining through automated means has collapsed with large language models. End-to-end encrypting such content provides no privacy; it only undermines users' security by creating false expectations and increases infrastructure operators' risks by making them unable to see what they deliver. + +[^ulysses]: Ulysses pact — adding constraints to reduce future options. Sé Reed used this analogy for the WordPress Foundation: tying the project to the mast before the siren songs of commercial capture (https://www.wpwatercooler.com/wpwatercooler/ep484-whose-wordpress-is-it-anyway/). diff --git a/blog/images/20260430-channel.png b/blog/images/20260430-channel.png new file mode 100644 index 0000000000..2600958dd0 Binary files /dev/null and b/blog/images/20260430-channel.png differ diff --git a/blog/images/20260430-home-light.png b/blog/images/20260430-home-light.png new file mode 100644 index 0000000000..c06f12e5e6 Binary files /dev/null and b/blog/images/20260430-home-light.png differ diff --git a/blog/images/20260430-home.png b/blog/images/20260430-home.png new file mode 100644 index 0000000000..df82b24c9e Binary files /dev/null and b/blog/images/20260430-home.png differ diff --git a/bots/api/COMMANDS.md b/bots/api/COMMANDS.md index ab3ec3d241..5ca2c4260a 100644 --- a/bots/api/COMMANDS.md +++ b/bots/api/COMMANDS.md @@ -1285,6 +1285,7 @@ Determine SimpleX link type and if the bot is already connected via this link. **Parameters**: - userId: int64 - connectionLink: string? +- resolveKnown: bool - linkOwnerSig: [LinkOwnerSig](./TYPES.md#linkownersig)? **Syntax**: diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 23fc79b634..ceb38939b1 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -95,6 +95,7 @@ This file is generated automatically. - [GroupInfo](#groupinfo) - [GroupKeys](#groupkeys) - [GroupLink](#grouplink) +- [GroupLinkOwner](#grouplinkowner) - [GroupLinkPlan](#grouplinkplan) - [GroupMember](#groupmember) - [GroupMemberAdmission](#groupmemberadmission) @@ -170,6 +171,7 @@ This file is generated automatically. - [SrvError](#srverror) - [StoreError](#storeerror) - [SubscriptionStatus](#subscriptionstatus) +- [SupportGroupPreference](#supportgrouppreference) - [SwitchPhase](#switchphase) - [TimedMessagesGroupPreference](#timedmessagesgrouppreference) - [TimedMessagesPreference](#timedmessagespreference) @@ -1846,6 +1848,7 @@ connFullLink + ((' ' + connShortLink) if connShortLink is not None else '') # Py ## E2EInfo **Record type**: +- public: bool? - pqEnabled: bool? @@ -2107,6 +2110,7 @@ Phone: - simplexLinks: [RoleGroupPreference](#rolegrouppreference) - reports: [GroupPreference](#grouppreference) - history: [GroupPreference](#grouppreference) +- support: [SupportGroupPreference](#supportgrouppreference) - sessions: [RoleGroupPreference](#rolegrouppreference) - comments: [CommentsGroupPreference](#commentsgrouppreference) - commands: [[ChatBotCommand](#chatbotcommand)] @@ -2198,6 +2202,7 @@ MemberSupport: - "simplexLinks" - "reports" - "history" +- "support" - "sessions" - "comments" @@ -2264,6 +2269,15 @@ MemberSupport: - acceptMemberRole: [GroupMemberRole](#groupmemberrole) +--- + +## GroupLinkOwner + +**Record type**: +- memberId: string +- memberKey: string + + --- ## GroupLinkPlan @@ -2290,6 +2304,9 @@ ConnectingProhibit: Known: - type: "known" - groupInfo: [GroupInfo](#groupinfo) +- groupUpdated: bool +- ownerVerification: [OwnerVerification](#ownerverification)? +- linkOwners: [[GroupLinkOwner](#grouplinkowner)] NoRelays: - type: "noRelays" @@ -2420,6 +2437,7 @@ NoRelays: - simplexLinks: [RoleGroupPreference](#rolegrouppreference)? - reports: [GroupPreference](#grouppreference)? - history: [GroupPreference](#grouppreference)? +- support: [SupportGroupPreference](#supportgrouppreference)? - sessions: [RoleGroupPreference](#rolegrouppreference)? - comments: [CommentsGroupPreference](#commentsgrouppreference)? - commands: [[ChatBotCommand](#chatbotcommand)]? @@ -2513,6 +2531,7 @@ Public: **Enum type**: - "channel" +- "group" --- @@ -3884,6 +3903,14 @@ NoSub: - type: "noSub" +--- + +## SupportGroupPreference + +**Record type**: +- enable: [GroupFeatureEnabled](#groupfeatureenabled) + + --- ## SwitchPhase diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index e2f67c88c6..933858c5cc 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -278,6 +278,7 @@ chatTypesDocsData = (sti @GroupKeys, STRecord, "", [], "", ""), (sti @GroupRootKey, STUnion, "GRK", [], "", ""), (sti @GroupLink, STRecord, "", [], "", ""), + (sti @GroupLinkOwner, STRecord, "", [], "", ""), (sti @GroupLinkPlan, STUnion, "GLP", [], "", ""), (sti @GroupMember, STRecord, "", [], "", ""), (sti @GroupMemberAdmission, STRecord, "", [], "", ""), @@ -294,7 +295,7 @@ chatTypesDocsData = (sti @GroupShortLinkInfo, STRecord, "", [], "", ""), (sti @GroupSummary, STRecord, "", [], "", ""), (sti @GroupSupportChat, STRecord, "", [], "", ""), - (sti @GroupType, STEnum1, "GT", ["GTUnknown"], "", ""), + (sti @GroupType, STEnum, "GT", ["GTUnknown"], "", ""), (sti @HandshakeError, STEnum, "", [], "", ""), (sti @InlineFileMode, STEnum, "IFM", [], "", ""), (sti @InvitationLinkPlan, STUnion, "ILP", [], "", ""), @@ -353,6 +354,7 @@ chatTypesDocsData = (sti @SrvError, STUnion, "SrvErr", [], "", ""), (sti @StoreError, STUnion, "SE", [], "", ""), (sti @SubscriptionStatus, STUnion, "SS", [], "", ""), + (sti @SupportGroupPreference, STRecord, "", [], "", ""), (sti @SwitchPhase, STEnum, "SP", [], "", ""), (sti @TimedMessagesGroupPreference, STRecord, "", [], "", ""), (sti @TimedMessagesPreference, STRecord, "", [], "", ""), @@ -482,6 +484,7 @@ deriving instance Generic GroupInfo deriving instance Generic GroupKeys deriving instance Generic GroupRootKey deriving instance Generic GroupLink +deriving instance Generic GroupLinkOwner deriving instance Generic GroupLinkPlan deriving instance Generic GroupMember deriving instance Generic GroupMemberAdmission @@ -561,6 +564,7 @@ deriving instance Generic SndGroupEvent deriving instance Generic SrvError deriving instance Generic StoreError deriving instance Generic SubscriptionStatus +deriving instance Generic SupportGroupPreference deriving instance Generic SwitchPhase deriving instance Generic TimedMessagesGroupPreference deriving instance Generic TimedMessagesPreference diff --git a/cabal.project b/cabal.project index 557dee3951..d059c07ab1 100644 --- a/cabal.project +++ b/cabal.project @@ -21,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 858fac7f4f821a2df6fbea03a1bfbb82ea9717c5 + tag: 1f173abf6d6fccb617be1e7994629c405983c431 source-repository-package type: git diff --git a/docs/protocol/channels-overview.md b/docs/protocol/channels-overview.md new file mode 100644 index 0000000000..d4cd2d2965 --- /dev/null +++ b/docs/protocol/channels-overview.md @@ -0,0 +1,341 @@ +Revision 1, 2026-04-28 + +# SimpleX Channels: stateful information delivery and management + +## Table of contents + +- [Introduction](#introduction) + - [What are SimpleX Channels](#what-are-simplex-channels) + - [Channels as transport layer](#channels-as-transport-layer) + - [Content visibility and participant privacy](#content-visibility-and-participant-privacy) + - [In comparison](#in-comparison) + - [Non-goals](#non-goals) +- [Architecture](#architecture) + - [State and distribution](#state-and-distribution) + - [Identity and ownership](#identity-and-ownership) + - [Governance](#governance) + - [Roles](#roles) +- [Cryptographic primitives](#cryptographic-primitives) +- [Security](#security) + - [Design objectives](#design-objectives) + - [Signing scope: roster only, content optional](#signing-scope-roster-only-content-optional) + - [Threat model](#threat-model) + - [Current gaps](#current-gaps) +- [Future work](#future-work) + - [Stateful access and history navigation](#stateful-access-and-history-navigation) + - [Transcript integrity](#transcript-integrity) + - [End-to-end encrypted side conversations](#end-to-end-encrypted-side-conversations) + - [Relay addition and removal](#relay-addition-and-removal) + - [Governance evolution](#governance-evolution) + - [Pre-moderation](#pre-moderation) + - [Scheduled delivery](#scheduled-delivery) + - [Link preview proxying](#link-preview-proxying) +- [Conclusion](#conclusion) + + +## Introduction + +The SimpleX network provides private point-to-point communication without user or endpoint identifiers, but most speech that matters is public. Every existing platform that distributes content at scale identifies both publishers and their audiences to the operator - none protect participation privacy. SimpleX Chat supported peer-to-peer groups, but they cannot scale to large audiences. SimpleX Channels close this gap. + +### What are SimpleX Channels + +SimpleX Channels are a stateful information delivery and management layer built on the [SimpleX network](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md). SMP queues provide stateless, unidirectional packet delivery between two endpoints. Channels add persistence, state, and scalable distribution - enabling one-to-many publishing with cryptographic identity independent of infrastructure operators. + +[SimpleX Chat](https://simplex.chat) is the first application, presenting channels as a broadcast publication model where owners publish and subscribers read, react, and comment. But channels are not limited to this use case - they are a general-purpose layer for distributing and managing stateful information (feeds, telemetry, automated pipelines, coordination services, social media). This document describes channels as a transport mechanism - the same mechanism will also be used for large groups, communities, wikis, forums, and other social media primitives. + +The critical difference from conventional publish-subscribe systems is that channel identity and governance are controlled cryptographically by the channel owners, not by the infrastructure operators. Relays - SimpleX network clients that forward and optionally cache channel content - can be added, removed, and replaced without changing the channel's identity, address, content, or cryptographic trust chain. A channel's relationship with its relays is transient; its identity is permanent. The authoritative record of content is hosted on channel owners' devices; relays perform transmission and caching similar to CDN infrastructure. + +Channel owners hold full control of the channel - its identity, content, governance rules, and membership - through self-custody of cryptographic keys. No infrastructure operator, relay provider, or third party can control or alter a channel without the owner's keys. Blockchain systems achieve a related property for financial assets - no third party can control holdings - through network-wide consensus. Channels achieve it through local authority and cryptographic signatures, without global consensus or a public ledger. Unlike blockchain state, channel state is mutable by the owner and not publicly verifiable by third parties. + +### Channels as transport layer + +The SimpleX network has three transport layers, each built on the one below: + +1. **SMP** ([SimpleX Messaging Protocol](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/simplex-messaging.md)) - stateless, unidirectional packet delivery between two endpoints through SMP routers. Provides fixed-size blocks, 2-node onion routing, and transport metadata protection. + +2. **SimpleX agents** ([agent protocol](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/agent-protocol.md)) - bidirectional, redundant connections between endpoints, with end-to-end post-quantum double ratchet encryption. The [SimpleX Chat Protocol](./simplex-chat.md) runs on top of this layer, providing direct messaging, group communication, and metadata delivery for file transfers via [XFTP protocol](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/xftp.md). + +3. **Channels** - stateful, one-to-many information delivery and management with cryptographic ownership and programmable governance. This layer runs on top of chat and agent layer 2, and it is described in this document. + +No network-wide user profile identifiers exist at any of these layers. Just as SMP enables private messaging by providing transport without user identifiers, channels enable public communication while preserving participation privacy at the distribution layer. + +Channel relays are themselves SimpleX clients in the SMP network, connecting to SMP routers using the same protocol, the same 2-node onion routing, and the same fixed-size transport blocks as any other endpoint. Even though the SMP network can distinguish a relay from a person's phone by its transport patterns, it prevents relays from learning anything about other network endpoints. In the case of SimpleX Chat, any CLI client can act as a chat relay without modifications. + +Channels therefore inherit all of SMP's transport privacy properties: + +- **Relays cannot observe subscriber network addresses.** The relay sees SMP queue addresses, not IP addresses or network sessions. The subscriber's IP is known only to their SMP router, which cannot see the message content (encrypted at the agent layer) or the IP addresses of whoever sends messages. + +- **SMP routers cannot see channel content.** Messages between relay and subscriber are end-to-end encrypted. The SMP router forwards fixed-size encrypted blocks without knowing whether they carry channel messages, direct messages, or anything else. + +- **Participation in multiple channels is unlinkable.** Each channel connection uses independent SMP queues with separate cryptographic credentials. Because of packet-level anonymity in 2-node routing, even if a subscriber uses the same SMP routers for all channels, the sending relays cannot determine this without collusion with those routers. Clients choose independently operated routers by default. + +No single point in the system sees both content and network identity. SMP routers see network addresses but not content, and no single SMP router can see which endpoints are communicating because clients choose independently operated routers. Relays see content but not network addresses. + +### Content visibility and participant privacy + +Any channel joinable via a public link, whether encrypted or not, must be considered completely public - the cost of joining through automated means has collapsed with large language models and is approaching zero. End-to-end encrypting such content provides no privacy; it only undermines users' security by creating false expectations and increases infrastructure operators' risks by making them unable to see what they deliver. Private channels with encrypted content are a separate use case discussed in [Future work](#end-to-end-encrypted-side-conversations). + +Content of public channels is therefore not end-to-end encrypted between owner and subscriber. Relays can read the messages they forward. Relay operators cannot undetectably alter channel content when multiple relays serve the channel, and cannot alter signed content at all - the authoritative state is held by owners. That each channel can use multiple chat relays provides both technical reliability and censorship resistance against any relay-specific content policies. + +The achievable privacy property for public communication is participation privacy - protecting who reads and writes content. The SMP transport carries no user identifiers, and relays are ordinary SMP clients, so subscribers connect without revealing their identity, network address, or any information that persists across channels. If an adversary joins a SimpleX channel, they see everything that is sent, but cannot determine who sent it or link any participant to anything outside the channel. + +Other systems make the opposite choice: content encryption in exchange for participant identification. For groups and channels joinable via public links this is the opposite of what is needed - the content encryption is meaningless (anyone can join and read), while the participant identification is the security threat. + +### In comparison + +**Telegram channels** - the operator controls channel identity (usernames are revocable), has full access to both content and participant identity. Channels cannot exist without Telegram's permission. + +**Nostr relays** - a single persistent key is used for publishing, following, and identity. Relays see content, the user's key, and their IP address. All posts and follow lists are signed and non-repudiable, linked to the same key - making both publishing and reading activity traceable and undeniable. + +**Signal groups** - content is end-to-end encrypted, but the operator manages group state and can observe the membership graph. Groups are capped at 1,000 members with no concept of a channel. + +**Matrix rooms** - server operators see room membership and metadata. Room identity is bound to the creating server's domain - if the server disappears, the room identity is lost. + +**Mastodon / ActivityPub** - publisher identity is bound to a server domain - if the server disappears, the identity is lost. Server operators see all content and all follower relationships. No encryption or privacy of any kind. + +| Property | Telegram | Nostr | Signal | Matrix | Mastodon | **SimpleX** | +|---|---|---|---|---|---|---| +| Content visible to operator | Yes | Yes | No | Configurable | Yes | **Yes** | +| Participant identity visible to operator | Yes | Yes | Yes | Yes | Yes | **No** | +| Channel identity independent of infrastructure | No | Yes | No | No | No | **Yes** | +| Sovereign ownership (no 3rd party can seize) | No | Yes | No | No | No | **Yes** | +| Programmable governance | No | No | No | No | No | **Planned** | +| Cryptographic content deniability | No | No | Yes | Yes | No | **Yes (default)** | +| Scalable one-to-many delivery | Yes | Yes | No | Limited | Yes | **Yes** | + +### Non-goals + +Channels do not attempt to: + +- **Encrypt public content from relay operators.** See [Content visibility and participant privacy](#content-visibility-and-participant-privacy). +- **Assign persistent identities to participants.** There are no usernames, public keys, or any identifiers that persist across channels or link activity across contexts. +- **Require network-wide consensus.** Channel state is authoritative on owner devices. The network does not validate channel transactions. +- **Guarantee immutability of content.** Channel state is fully controlled and mutable by owners, unlike blockchain state, which is immutable by design. + +## Architecture + +The introduction established what channels provide and why. This section describes how: where state lives, how identity and ownership work, how governance evolves, and what each participant does. + +### State and distribution + +The authoritative record of a channel - content, member roster, profile, cryptographic keys, governance rules - is held by channel owners on their own devices, not on relays, not on any server, and not on any shared ledger. Relays hold transient copies for distribution and optional caching, analogous to CDN edge nodes: the origin holds the truth, CDN nodes come and go. Consensus is only required between channel owners, not across the entire network. + +``` + ┌──────────┐ + │ Owner │ <- authoritative state + └────┬─────┘ + │ + ┌───────────┼───────────┐ + │ │ │ + ┌────▼───┐ ┌────▼───┐ ┌────▼───┐ + │Relay A │ │Relay B │ │Relay C │ <- cache / distribution + └────┬───┘ └────┬───┘ └────┬───┘ + │ │ │ + ┌─────┼─────┐ ... ┌─────┼─────┐ + │ │ │ │ │ │ + S1 S2 S3 S7 S8 S9 <- received copies +``` + +Content originates on the owner's device and flows through relays to subscribers. Each relay independently forwards to all of its subscribers. Subscribers do not connect to owners or to each other - this provides better scalability than peer-to-peer SimpleX groups, where adding a member requires N new connections. When multiple relays serve the same channel, subscribers deduplicate at the client level. + +**Failure modes:** + +- **Loss of a relay is loss of a cache node, not loss of data.** The owner can send the same content through a replacement relay. + +- **Loss of all owner devices is the catastrophic event** - relay caches become orphaned and the channel's private keys are gone. Multiple owners and backups mitigate this risk. + +- **Disagreements between relays are resolved by the origin.** The owner's version is authoritative, settling cache inconsistency through any reachable relay. + +Subscribers hold their own received copies. Signed messages are independently verifiable without consulting the relay or owner. Unsigned content depends on cross-relay consistency or future transcript integrity mechanisms. + +### Identity and ownership + +A channel's identity is the SHA-256 hash of the genesis root public key, computed at creation time and never changed - even if relays are added, removed, or the channel link is rotated. It is self-authenticating: derived from a key pair that only the channel creator held. It is embedded in the channel's link, distributed in the profile to all members, and used as a binding prefix in all signed messages. + +Subscribers validate that the identity in the link matches the identity in the profile, preventing link substitution. Profile updates that attempt to change the identity are rejected. Full validation that the identity equals the hash of the root key is deferred: if current clients enforced this check, they would reject future rotated links as invalid. The identity is correctly managed today; validation will be enforced with the key rotation protocol. See the [group identity binding RFC](../rfcs/2026-03-28-group-identity-binding.md). + +The root key does not sign messages directly. Instead, it authorizes owner keys through a signed chain. At creation, the owner generates a root key pair and a separate member key pair for signing. The member key is published as an authorization entry signed by the root key. New owners can be added by any previously authorized owner signing a new entry. Anyone retrieving the channel link can verify this chain without network access. + +The root key is a bootstrap key - it certifies owners, then need not be used again. All owners are cryptographically indistinguishable to subscribers (they all have equally valid authorization chains), which - provided multiple owners were signed by the root key - conceals the creator's identity. + +The channel link is the out-of-band trust anchor - relays and SMP routers cannot modify link content. All members announce their signing keys on joining. Owner keys are verifiable against the link. Role changes (promoting members to admin, moderator) are signed by owners at the protocol level. + +A planned extension will record role changes as a linearly ordered signed roster log with consistent sequencing across all owners, relays, and subscribers. This linearization prevents ambiguous roster states from concurrent unordered changes, and creates a verifiable chain of trust from the channel link through owners to all elevated roles. Out-of-band key verification for non-owner members will further extend this to E2E encrypted conversations. + +### Governance + +"Management" in "information delivery and management" refers not only to managing content but to managing the channel itself - who can make decisions, and how. + +The low-level protocol supports multiple owners from the initial release. The application-level governance model evolves through a planned progression: + +**Current (v6.5): Single owner.** One owner controls the channel. All administrative actions (profile changes, roster modifications, relay management) are decided by this single owner. The protocol-level owners chain supports verification of multiple entries, but the application creates and manages only one. + +**Near-term (v7): Multiple owners, any-owner-decides.** Multiple owners share control of the channel. Any owner can independently make any administrative decision - add or remove members, change the profile, manage relays. This is the most common decision-making model in practice (equivalent to "all admins are equal" in most online platforms). No coordination between owners is required for any action. + +**Future: Multisig and programmable governance.** Further stages include M-of-N multisig for administrative actions and, eventually, programmable governance rules defined as code in the channel's definition. The protocol must support these without prescribing a specific governance model. + +### Roles + +- **Owners** create the channel, hold the authoritative state and private keys on their devices, publish content, and manage the member roster. Owners sign administrative messages and optionally content messages. A channel must have at least one owner. + +- **Relays** receive content from owners and members with posting rights, optionally cache it, and forward it to subscribers. They accept new subscriber connections and introduce them to the channel owners. Relays cannot author messages. A channel must have at least one active relay. Relays are ordinary SimpleX clients - a relay can be operated by anyone (a channel operator, a third-party service provider, or a self-hosted instance) and each creates its own contact address link, bound to the channel's identity. The relay's relationship with the channel is transient - owners can add and remove relays without changing the channel's identity. + +- **Subscribers** connect to relays and receive content. They cannot send messages by default, but can be given posting rights. + +Additional roles (moderator, admin, member, author) exist in the hierarchy and are inherited from the group protocol. + +For protocol-level detail - wire formats, message types, signing and verification mechanics, delivery pipeline - see [SimpleX Channels Protocol](./channels-protocol.md). + + +## Cryptographic primitives + +- **Ed25519** - channel identity (root key pair), owner authorization chain, and message signing. The signature binding prefix includes the channel's entity ID and the sender's member ID, preventing cross-channel replay. + +- **SHA-256** - derives the channel's entity ID from the genesis root public key. Immutable, serves as the channel's permanent identity. + +- **Double ratchet with post-quantum KEM** (inherited from [SimpleX agent layer](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/agent-protocol.md)) - end-to-end encryption for all SMP transport. Not channel-specific - channels inherit it by being built on the agent layer. Future E2E side conversations (support scope, member DMs, private channels) will use the same mechanism. + +Content messages are not signed by default to preserve cryptographic deniability - see [Signing scope](#signing-scope-roster-only-content-optional). Owners may opt into signing all content in a future release. + + +## Security + +This section examines what the architectural properties protect against, where they hold, and where gaps remain. + +### Design objectives + +The channel protocol is designed to achieve the following security objectives: + +1. **Stable message delivery** between channel participants, resilient to individual relay failures. +2. **No possibility for a relay to substitute the channel** - the channel's identity is cryptographically bound to the link and profile controlled by channel owners. +3. **No possibility for a relay to impersonate an owner** - administrative messages require valid signatures. +4. **Prevention of relay-initiated roster manipulation** - member removal, role changes, and other roster modifications require valid owner signatures. +5. **Relay transience** - the owner can add and remove relays, including the last relay, without permanently losing the channel. Subscribers can restore connectivity by retrieving updated link data. +6. **Sender anonymity within multi-owner channels** - owners can publish as the channel, hiding which specific owner authored a message from subscribers. +7. **Participant privacy** - relay operators cannot determine subscriber identity or network address, and subscribers cannot determine each other's identity. This is inherited from the SMP transport layer. + +### Signing scope: roster only, content optional + +By default, only roster-modifying and administrative messages are signed. Content messages are not signed. Two reasons: + +1. **Cryptographic deniability.** Signing creates non-repudiable proof of authorship verifiable by any third party. Without signatures, no such proof exists - a relay could have fabricated any unsigned message. + +2. **Proportional defense.** Changes to roster, channel profile, and permissions can be disruptive and irreversible - they must be authenticated at processing time. Content manipulation is detectable post-hoc through cross-relay consistency, and the authoritative record on the owner's device is unaffected. + +Owners will be able to opt into signing content on a per-channel or per-message basis - some publishers want non-repudiable authorship, others prefer deniability. + +### Threat model + +This threat model assumes the [SimpleX network threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/security.md) and addresses threats specific to the channel layer. + +**A single compromised relay** + +*can:* + +- Substitute unsigned content or selectively drop messages for its subscribers. Detectable by subscribers connected to other relays - the owner's version is authoritative. TODO: difference detection not yet implemented. +- Selectively target specific subscribers while delivering correctly to others. +- Ignore the "message from channel" directive, revealing which owner sent a message. Detectable out-of-band. +- Fabricate or hide subscriber connections, inflating or deflating counts. Detectable if subscribers are connected to other relays. + +*cannot:* + +- Undetectably substitute content - subscribers on honest relays receive the original. +- Alter the channel's authoritative state on the owner's device. +- Substitute the channel profile or impersonate an owner - these require valid signatures. +- Redirect subscribers to a different channel - the entity ID is validated across link and profile. +- Determine subscriber identity or network address - inherited from SMP transport. +- Correlate subscriber participation across channels - each connection uses independent SMP queues. The subscriber chooses their SMP router independently, so collusion between a relay and the relay's SMP router does not compromise connections through a different router. + +**All relays compromised and colluding** + +*can:* + +- Undetectably substitute unsigned content for all subscribers, unless owners sign content messages. +- Prevent delivery of any messages, including signed ones (signing prevents substitution, not dropping). +- Fabricate or hide subscriber connections undetectably. + +*cannot:* + +- Forge signed administrative messages or substitute the channel profile. +- Alter the authoritative state on the owner's device. + +**Compromise of owner keys** + +An attacker who obtains the root private key or an owner's member private key (through device compromise, backup theft, or coercion) can impersonate the owner and sign arbitrary administrative messages. This is a different threat from key loss - the channel continues operating, but under adversarial control. Mitigation depends on owner-side operational security and future multisig governance. For the threat model of the channel link itself (the trust anchor), see the [short links for groups RFC](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2025-04-04-short-links-for-groups.md). + +**Loss of all owner devices** + +The channel can have no new content, no administrative updates, no new owners. Relay caches continue delivering existing content but cannot be refreshed, and will eventually expire in the absence of the owner connection. Multiple owners and key backups mitigate this risk. + +**A subscriber** + +*can:* + +- See all public content, by design. +- Join multiple times with different profiles, inflating counts. + +*cannot:* + +- Identify other subscribers, send messages to the channel (unless given posting rights), or forge messages of the owner or other subscribers. + +**A passive network observer** + +*can:* + +- Observe communication with an SMP router, but not whether it is channel-related. + +*cannot:* + +- Determine which channel a subscriber uses, correlate channel activity with other SimpleX activity, or identify a relay as distinct from an ordinary user, other than by traffic volume. Inherited from SMP transport. + +### Current gaps + +1. **Cross-relay consistency detection.** Duplicate messages are silently deduplicated without hash comparison. Designed but not implemented. +2. **Link entity ID validation.** Deferred to a future version with key rotation. See [group identity binding RFC](../rfcs/2026-03-28-group-identity-binding.md). +3. **Multi-relay UX.** Protocol supports multiple relays per subscriber; no UX for monitoring relay-level delivery health. It will be added in v6.5.x. + + +## Future work + +### Stateful access and history navigation + +Currently, relays send recent cached history on join but do not support navigation or search. Planned: history pagination by timestamp or message ID, remote search against relay caches, and selective retrieval of specific message ranges. Relay operators can differentiate on cache depth and search capabilities. + +### Transcript integrity + +- **Opt-in content signing.** Per-channel or per-message choice to sign content, making it non-repudiable. This will be released in SimpleX Chat v7. +- **Subscriber transcript acknowledgment.** Subscribers periodically sign a digest of received history ("I've seen it" rather than "I've authored it"), enabling detection of relay manipulation through diverging digests. +- **Merkle tree signing.** Owner periodically publishes a signed Merkle root. Subscribers verify their copies against the owner's authoritative record. + +### End-to-end encrypted side conversations + +- **E2E encrypted support scope** between subscriber and moderator/owner. +- **E2E encrypted DMs between members** where channel settings permit, using standard SimpleX connection establishment. +- **Private channels** where the entire content stream is encrypted to authorized subscribers. The relay becomes a conduit that sees neither content nor identity. + +### Relay addition and removal + +Dynamic relay addition with cache population from existing relays or owner. Relay removal with subscriber migration. Relay rotation with continuity - new relay connects before old relay is removed. It will be added in v6.5.x. + +### Governance evolution + +- **Multiple owners (v7):** concurrent administrative authority, any owner acts independently. +- **Multisig:** M-of-N approval for administrative actions, with per-action quorums. +- **Programmable governance:** rules defined as code in the channel definition. + +### Pre-moderation + +Subscriber messages reviewed by moderators before becoming visible to all subscribers. + +### Scheduled delivery + +Messages scheduled for future delivery, cached by relay until the scheduled time. + +### Link preview proxying + +The relay loads link previews on behalf of the sender - it already sees message content, so it learns nothing new, and unlike the sender its IP is not linked to any identity. + + +## Conclusion + +SimpleX Channels enable a publisher to reach an unlimited audience without any infrastructure operator knowing who that audience is. No third party can seize the channel because owners hold the keys and the authoritative state on their own devices - relays only cache and forward. Owner signatures protect content integrity and the trust chain extends to all administrative roles. These properties require a network without participant identifiers - they cannot be added to a system that has them. diff --git a/docs/protocol/channels-protocol.md b/docs/protocol/channels-protocol.md new file mode 100644 index 0000000000..979ab2c85b --- /dev/null +++ b/docs/protocol/channels-protocol.md @@ -0,0 +1,243 @@ +Revision 1, 2026-04-28 + +# SimpleX Channels Protocol + +For architecture, design rationale, security properties, and threat model, see [SimpleX Channels Overview](./channels-overview.md). + +## Table of contents + +- [Protocol](#protocol) + - [Channel creation](#channel-creation) + - [Relay acceptance](#relay-acceptance) + - [Subscriber connection](#subscriber-connection) + - [Message signing](#message-signing) + - [Message forwarding](#message-forwarding) + - [Binary batch format](#binary-batch-format) + - [Delivery pipeline](#delivery-pipeline) + - [Message deduplication](#message-deduplication) + - [Channel-as-sender messages](#channel-as-sender-messages) + - [Member support scope](#member-support-scope) + + +## Protocol + +This document describes the channel protocol as currently implemented. It builds on the [SimpleX Chat Protocol](./simplex-chat.md), using the same message format and connection model, with extensions for relay-mediated distribution and cryptographic message signing. + +### Channel creation + +Creating a channel involves generating cryptographic material, creating the channel link, and connecting relay members: + +1. **Key generation.** The owner generates an Ed25519 root key pair. The entity ID is computed as `sha256(rootPubKey)`. A separate member key pair is generated for message signing, and an `OwnerAuth` entry is created, signed by the root key. + +2. **Link creation.** The owner calls the agent's `prepareConnectionLink` API with the root key pair and entity ID. This returns a prepared link (including a `ConnShortLink` address) without any network calls. The link address is deterministic, derived from the fixed data hash, so it can be embedded in the group profile immediately. + +3. **Link data upload.** The owner calls `createConnectionForLink`, which makes a single network call to create the SMP queue and upload the encrypted link data. The link's fixed data contains the root public key and connection request. The mutable user data contains the `OwnerAuth` array, the channel profile (including the entity ID and the link itself), and the initial subscriber count. + +4. **Relay invitation.** For each selected relay, the owner sends a contact request containing an `x.grp.relay.inv` message with the channel's short link. The relay retrieves the link data, validates the channel profile, creates its own relay link (with the channel's entity ID in its immutable data), and responds with `x.grp.relay.acpt` containing its relay link. + +5. **Link update.** As each relay accepts and provides its relay link, the owner validates that the relay link contains the correct entity ID, then adds the relay link to the channel link's mutable data. + +6. **Local record.** The channel is stored on the owner's device with the root private key, member private key, and channel profile. This local record is the authoritative state of the channel. + +### Relay acceptance + +When a relay receives an invitation to serve a channel, it validates the channel and creates its own relay link. This flow is currently part of channel creation; adding relays to an existing channel is planned but not yet implemented. + +1. Owner sends `x.grp.relay.inv` to the relay's contact address. This message includes the relay's member ID and role, the owner's profile, and the channel's short link. + +2. Relay receives the invitation and creates a relay request record. A relay request worker processes it asynchronously. + +3. The worker retrieves the channel's link data from the SMP server, extracts and validates the channel profile and owner authorization. + +4. The relay creates its own contact address link (the relay link) with the channel's entity ID in the immutable fixed data. + +5. The relay accepts the owner's connection request, sending its relay link in the acceptance. + +6. The owner retrieves the relay link data, validates that the entity ID in the relay link matches the channel's entity ID, and adds the relay link to the channel link's user data. + +TODO: Periodic monitoring where the relay retrieves channel link data to verify its relay link is still listed is planned but not yet implemented. + +### Subscriber connection + +A subscriber joins a channel through the following flow: + +1. **Link retrieval.** The subscriber scans or receives the channel's short link. The client retrieves the link data, which contains the channel profile, owner authorization chain, and list of relay links. + +2. **Relay link resolution.** For each relay link listed, the client resolves the `ConnectionRequestUri` from the relay's short link. + +3. **Connection.** The client connects to relays - the first synchronously, the rest asynchronously. Each connection sends an `x.member` message with the subscriber's profile (or an incognito profile, created once and shared with all relays), member ID, and member signing key. + +4. **Relay acceptance.** Each relay accepts the connection, creates a member record for the subscriber with the configured subscriber role (default `observer`), and sends an `x.grp.link.inv` message with the channel profile and group link invitation data. + +5. **Introduction.** The relay introduces the new subscriber to the channel's moderators and owners by sending an `x.grp.mem.new` message. It also sends moderator/owner profiles to the subscriber. + +6. **History.** If the channel has history sharing enabled, the relay sends recent cached history to the new subscriber. + +The subscriber is functional (can receive messages) as soon as at least one relay connection succeeds. Additional relay connections provide redundancy and cross-relay consistency checking. + +### Message signing + +Messages that alter the channel's roster, profile, or administrative state are cryptographically signed by the sending owner. Content messages are not signed by default; see [Signing scope](#signing-scope-roster-only-content-optional) for the rationale. + +**Which messages require signatures:** + +| Message | Description | Signed | +|---|---|---| +| `x.grp.del` | Delete channel | Required | +| `x.grp.info` | Update channel profile | Required | +| `x.grp.prefs` | Update channel preferences | Required | +| `x.grp.mem.del` | Remove member | Required | +| `x.grp.mem.role` | Change member role | Required | +| `x.grp.mem.restrict` | Restrict member | Required | +| `x.grp.leave` | Leave channel | Required (unverified allowed between subscribers) | +| `x.info` | Update member profile | Required (unverified allowed between subscribers) | +| `x.msg.new` | Content message | Not signed | +| `x.msg.update` | Edit message | Not signed | +| `x.msg.del` | Delete message | Not signed | + +**Signing process:** + +The signing context binds the signature to a specific channel and sender: + +``` +bindingPrefix = smpEncode(CBGroup) <> smpEncode(publicGroupId, memberId) +signedBytes = bindingPrefix <> messageBody +signature = Ed25519.sign(memberPrivKey, signedBytes) +``` + +The binding prefix includes the chat binding tag (`"G"` for group), the channel's entity ID, and the sender's member ID. This prevents cross-channel and cross-member replay attacks - a signature valid in one channel cannot be reused in another. + +**Verification process:** + +When a subscriber receives a signed message: + +1. The signature is present: reconstruct the binding prefix from the channel's stored entity ID and the sender's member ID. Verify all signatures against the sender's stored public key. If all verify, the message is accepted as verified. + +2. The signature is present but the sender's key is unknown: the message is accepted as signed-but-unverified only if the event does not require a signature. For `x.grp.leave` and `x.info` between subscribers whose keys haven't been exchanged yet, unverified signatures are permitted as a temporary measure. + +3. No signature is present: the message is accepted only if the event does not require a signature (i.e., the channel does not use relays, or the event is a content message). + +If verification fails for a message that requires a signature, the message is rejected and a bad signature event is shown to the user. + +### Message forwarding + +Content originates on the owner's device and flows through relays to subscribers. The forwarding mechanism preserves the original message bytes, including any signature, without re-encoding: + +**Owner to Relay:** The owner sends messages directly to each relay over their SMP connection. Messages are encoded in binary batch format. + +**Relay processing:** When a relay receives a message from an owner, it: + +1. Parses and processes the message locally (updating its cached state, e.g. for roster changes). +2. If the relay is configured to forward for this channel, creates a **delivery task** for each message that should be forwarded to subscribers. The task records the message ID, the sender's member ID, the broker timestamp, and whether the message was sent as the channel (not attributed to a specific owner). +3. The delivery task is persisted to the database for delivery reliability - ensuring forwarding can resume after a relay crash. + +**Relay to Subscribers:** A delivery task worker reads pending tasks, batches them into delivery jobs, and a delivery job worker sends each job to subscribers in paginated batches (using a cursor over group member IDs). + +For forwarded messages from subscribers to owners (e.g. support scope messages), the relay wraps the message in a forwarding envelope: + +``` +forwardEnvelope = ">" <> smpEncode(GrpMsgForward) <> encodeBatchElement(signedMsg, msgBody) +``` + +This preserves the original message's signature bytes verbatim. + +### Binary batch format + +Channels use a binary batch format that preserves exact message bytes for signature verification. This is distinct from the JSON array batching used by regular groups. + +```abnf +binaryBatch = %s"=" elementCount *batchElement +elementCount = 1*1 OCTET ; 1-255 elements +batchElement = elementLen elementBody +elementLen = 2*2 OCTET ; 16-bit big-endian length + +elementBody = signedElement / forwardElement / plainElement / fileElement + +signedElement = %s"/" chatBinding sigCount *msgSignature jsonBody +forwardElement = %s">" grpMsgForward (signedElement / plainElement) +plainElement = %s"{" *OCTET ; JSON message body +fileElement = %s"F" *OCTET ; binary file chunk + +chatBinding = 1*1 OCTET ; "G" (group), "D" (direct), "C" (channel) +sigCount = 1*1 OCTET ; number of signatures (1-255) +msgSignature = keyRef sigBytes +keyRef = %s"M" ; member key reference +sigBytes = 64*64 OCTET ; Ed25519 signature + +grpMsgForward = fwdSender brokerTs +fwdSender = memberFwd / channelFwd +memberFwd = %s"M" memberId memberName ; attributed to specific member +channelFwd = %s"C" ; attributed to channel as sender +brokerTs = 8*8 OCTET ; UTC system time +``` + +The parser (`parseChatMessages`) dispatches on the first byte: + +- `'='` -> binary batch (new format, used by channels) +- `'X'` -> compressed (decompress, then re-parse) +- `'['` -> JSON array (legacy group format) +- `'{'` -> single JSON message + +Forward elements contain the original message bytes verbatim. The relay does not re-encode the inner message. This is what makes signature verification possible after forwarding: the exact bytes that were signed by the owner are preserved through the relay. + +Nested forwarding (`>` inside `>`) is explicitly rejected by the parser. + +### Delivery pipeline + +The relay's delivery pipeline has two stages, both backed by persistent database tables for delivery reliability (not for authoritative storage - the relay's database is a delivery queue, not a content database): + +**Stage 1: Delivery tasks.** When the relay receives a message from an owner that should be forwarded, it creates a `delivery_task` record: + +``` +delivery_task: + group_id, worker_scope, job_scope, + sender_group_member_id, message_id, + message_from_channel (bool), + task_status (new -> processed) +``` + +A **task worker** (one per group per scope) reads pending tasks, batches multiple tasks into a single binary batch body, and creates a delivery job. + +**Stage 2: Delivery jobs.** A delivery job contains the pre-encoded batch body and a cursor for paginated delivery: + +``` +delivery_job: + group_id, worker_scope, job_scope, + body (pre-encoded binary batch), + cursor_group_member_id, + job_status (pending -> complete) +``` + +A **job worker** reads the body and delivers it to subscribers in paginated batches. For each page, it loads a bucket of subscribers by cursor position, sends the body to all of them, advances the cursor, and continues until all subscribers have been served. This avoids loading all subscribers into memory at once. + +For subsequent subscribers in a batch, the agent uses a value reference to the first subscriber's message body, avoiding redundant data transmission to the SMP server. + +### Message deduplication + +When multiple relays serve the same channel, each subscriber receives the same message from each relay independently. Deduplication is performed at the subscriber's client level using the message's shared message ID: + +- When saving a received message, the client checks whether a message with the same shared ID already exists for this group. +- If a duplicate is found, the message is silently dropped (in channels with relays). +- In non-relay groups, duplicate detection triggers a `x.grp.mem.con` notification to the forwarding member. + +This is essentially cache coherence verification - comparing what was received from one cache node against another. TODO: Currently, deduplication only detects the presence of duplicates. The protocol design includes provisions for detecting differences between relay-delivered copies of the same message (hash comparison, UI indicators for discrepancies). This is described in the [channels forwarding RFC](../rfcs/2025-08-11-channels-forwarding.md) and is not yet implemented. + +### Channel-as-sender messages + +Owners can send messages attributed to the channel rather than to themselves. When `asGroup = True` is set in the message container, the relay forwards the message with a channel-as-sender tag instead of attributing it to a specific member. On the subscriber side, such messages are displayed as coming from the channel (using the channel's profile image and name) rather than from a specific owner. + +This will be useful for channels with multiple owners (not yet implemented at application level) where the identity of the specific sender should not be visible to subscribers. The relay must respect this directive; ignoring it and revealing the sending owner's identity is a threat vector (detectable out-of-band by members communicating with the owner). + +The forwarding binding prefix for channel-as-sender messages uses `CBChannel` instead of `CBGroup`, and includes only the channel's entity ID (not the sender's member ID): + +``` +channelBinding = smpEncode(CBChannel) <> smpEncode(publicGroupId) +``` + +### Member support scope + +Channels support a **member support scope** - a private side-channel between a subscriber and the channel's moderators/owners. Messages sent in the support scope are delivered only to moderators and the scoped subscriber, not to all subscribers. + +A support-scoped message includes the target member's ID. The delivery pipeline uses a separate job scope for support messages, loading only the scoped member and moderators rather than all subscribers. + +Support scope messages are visible only to the subscriber who initiated the support conversation and to the channel's moderators. Other subscribers cannot see them. This allows subscribers to report issues, appeal moderation decisions, or communicate with administrators without revealing their identity to other subscribers. diff --git a/docs/protocol/simplex-chat.md b/docs/protocol/simplex-chat.md index c74465f6fe..040064864c 100644 --- a/docs/protocol/simplex-chat.md +++ b/docs/protocol/simplex-chat.md @@ -266,6 +266,12 @@ Currently members can have one of four roles - `owner`, `admin`, `member` and `o `x.grp.msg.forward` message is sent by inviting member to forward messages between introduced members, while they are connecting. +### Channels: relay-mediated groups + +Channels are groups where message delivery is mediated by dedicated relay members rather than by direct connections between all members. Channels extend the group sub-protocol with additional roles (`relay`, `observer`), message signing for administrative actions, a binary batch format for signed and forwarded messages, and an asynchronous delivery pipeline. + +For architecture and design rationale, see [SimpleX Channels Overview](./channels-overview.md). For protocol-level detail - wire formats, message types, signing mechanics, delivery pipeline - see [SimpleX Channels Protocol](./channels-protocol.md). + ## Sub-protocol for WebRTC audio/video calls This sub-protocol is used to send call invitations and to negotiate end-to-end encryption keys and pass WebRTC signalling information. @@ -282,12 +288,13 @@ These message are used for WebRTC calls: ## Threat model -This threat model compliments SMP, XFTP, push notifications and XRCP protocols threat models: +This threat model complements SMP, XFTP, push notifications and XRCP protocols threat models, as well as the channel-specific threat model: - [SimpleX Messaging Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md#threat-model); - [SimpleX File Transfer Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xftp.md#threat-model); - [Push notifications threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/push-notifications.md#threat-model); -- [SimpleX Remote Control Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xrcp.md#threat-model). +- [SimpleX Remote Control Protocol threat model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/xrcp.md#threat-model); +- [SimpleX Channels threat model](./channels-overview.md#threat-model). #### A user's contact @@ -342,3 +349,27 @@ This threat model compliments SMP, XFTP, push notifications and XRCP protocols t *cannot:* - prove that two group members with incognito profiles is the same user. + +#### A channel relay + +For the full channel threat model, see [SimpleX Channels: threat model](./channels-overview.md#threat-model). + +*can:* + +- send arbitrary unsigned content messages to subscribers, effectively fabricating the content stream while the channel identity and signed profile remain intact. + +- selectively drop any messages, both content and signed administrative events, for some or all subscribers. + +- ignore the "message from channel" directive, revealing which specific owner sent a message. + +- fabricate subscriber connections, inflating subscriber counts. + +*cannot:* + +- impersonate an owner - administrative messages (roster changes, profile updates, channel deletion) require valid cryptographic signatures that the relay cannot produce. + +- substitute the channel profile - profile changes require a valid owner signature. + +- redirect joining subscribers to a different channel - the channel's entity ID is baked into both the channel link and the relay link's immutable data. + +- determine the real-world identity of subscribers - subscriber connections carry no persistent identity. diff --git a/packages/simplex-chat-client/types/typescript/package.json b/packages/simplex-chat-client/types/typescript/package.json index 8d05eb460c..329003a6b8 100644 --- a/packages/simplex-chat-client/types/typescript/package.json +++ b/packages/simplex-chat-client/types/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@simplex-chat/types", - "version": "0.4.0", + "version": "0.5.0", "description": "TypeScript types for SimpleX Chat bot libraries", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/simplex-chat-client/types/typescript/src/commands.ts b/packages/simplex-chat-client/types/typescript/src/commands.ts index 36692739dd..9c5c31ceb2 100644 --- a/packages/simplex-chat-client/types/typescript/src/commands.ts +++ b/packages/simplex-chat-client/types/typescript/src/commands.ts @@ -471,6 +471,7 @@ export namespace APIAddContact { export interface APIConnectPlan { userId: number // int64 connectionLink?: string + resolveKnown: boolean linkOwnerSig?: T.LinkOwnerSig } diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 6f4f0b6525..eed9d5edc1 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2094,6 +2094,7 @@ export interface DroppedMsg { } export interface E2EInfo { + public?: boolean pqEnabled?: boolean } @@ -2442,6 +2443,7 @@ export interface FullGroupPreferences { simplexLinks: RoleGroupPreference reports: GroupPreference history: GroupPreference + support: SupportGroupPreference sessions: RoleGroupPreference comments: CommentsGroupPreference commands: ChatBotCommand[] @@ -2515,6 +2517,7 @@ export enum GroupFeature { SimplexLinks = "simplexLinks", Reports = "reports", History = "history", + Support = "support", Sessions = "sessions", Comments = "comments", } @@ -2565,6 +2568,11 @@ export interface GroupLink { acceptMemberRole: GroupMemberRole } +export interface GroupLinkOwner { + memberId: string + memberKey: string +} + export type GroupLinkPlan = | GroupLinkPlan.Ok | GroupLinkPlan.OwnLink @@ -2610,6 +2618,9 @@ export namespace GroupLinkPlan { export interface Known extends Interface { type: "known" groupInfo: GroupInfo + groupUpdated: boolean + ownerVerification?: OwnerVerification + linkOwners: GroupLinkOwner[] } export interface NoRelays extends Interface { @@ -2706,6 +2717,7 @@ export interface GroupPreferences { simplexLinks?: RoleGroupPreference reports?: GroupPreference history?: GroupPreference + support?: SupportGroupPreference sessions?: RoleGroupPreference comments?: CommentsGroupPreference commands?: ChatBotCommand[] @@ -2776,6 +2788,7 @@ export interface GroupSupportChat { export enum GroupType { Channel = "channel", + Group = "group", } export enum HandshakeError { @@ -4627,6 +4640,10 @@ export namespace SubscriptionStatus { } } +export interface SupportGroupPreference { + enable: GroupFeatureEnabled +} + export enum SwitchPhase { Started = "started", Confirmed = "confirmed", diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md index e75dab3f96..2132c47a79 100644 --- a/packages/simplex-chat-nodejs/README.md +++ b/packages/simplex-chat-nodejs/README.md @@ -14,7 +14,7 @@ Please share your use cases and implementations. ## Quick start: a simple bot ``` -npm i simplex-chat@6.5.0-beta.4.4 +npm i simplex-chat@6.5.0-beta.10 ``` Simple bot that replies with squares of numbers you send to it: @@ -26,7 +26,7 @@ Simple bot that replies with squares of numbers you send to it: // const {bot} = await import("../dist/index.js") const [chat, _user, _address] = await bot.run({ profile: {displayName: "Squaring bot example", fullName: ""}, - dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"}, options: { addressSettings: {welcomeMessage: "Send a number, I will square it.", }, @@ -62,6 +62,44 @@ There is an example with more options in [./examples/squaring-bot.ts](./examples You can run it with: `npx ts-node ./examples/squaring-bot.ts` +## PostgreSQL backend + +By default, the package uses SQLite. To use PostgreSQL instead: + +```bash +npm install simplex-chat --simplex_backend=postgres +``` + +Or persist the setting in `.npmrc`: + +```ini +simplex_backend=postgres +``` + +### Prerequisites (PostgreSQL) + +- `libpq5` must be installed on the host system (`apt install libpq5` on Debian/Ubuntu) +- PostgreSQL backend is only available for Linux x86_64 +- A PostgreSQL server accessible via connection string + +### Passing PostgreSQL connection + +The `DbConfig` type is a discriminated union — pick the variant that matches +the backend you installed: + +```ts +// SQLite (default) +dbOpts: {type: "sqlite", filePrefix: "./data/bot"} +// optional: encryptionKey: "" + +// PostgreSQL +dbOpts: { + type: "postgres", + connectionString: "postgres://user:pass@host/db", + // schemaPrefix: "bot", // optional — defaults to "simplex_v1" +} +``` + ## Documentation The library docs are [here](./docs/README.md). diff --git a/packages/simplex-chat-nodejs/docs/Namespace.api.md b/packages/simplex-chat-nodejs/docs/Namespace.api.md index a1b3d2ca5a..5771b8450e 100644 --- a/packages/simplex-chat-nodejs/docs/Namespace.api.md +++ b/packages/simplex-chat-nodejs/docs/Namespace.api.md @@ -24,6 +24,7 @@ You need to use it in bot event handlers, and for any other use cases. ## Type Aliases +- [DbConfig](api.TypeAlias.DbConfig.md) - [EventSubscriberFunc](api.TypeAlias.EventSubscriberFunc.md) - [EventSubscribers](api.TypeAlias.EventSubscribers.md) diff --git a/packages/simplex-chat-nodejs/docs/Namespace.bot.md b/packages/simplex-chat-nodejs/docs/Namespace.bot.md index 447b0b6f68..4b9171d0f7 100644 --- a/packages/simplex-chat-nodejs/docs/Namespace.bot.md +++ b/packages/simplex-chat-nodejs/docs/Namespace.bot.md @@ -12,9 +12,12 @@ It automates creating and updating of the bot profile, address and bot commands ## Interfaces - [BotConfig](bot.Interface.BotConfig.md) -- [BotDbOpts](bot.Interface.BotDbOpts.md) - [BotOptions](bot.Interface.BotOptions.md) +## Type Aliases + +- [BotDbOpts](bot.TypeAlias.BotDbOpts.md) + ## Functions - [run](bot.Function.run.md) diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md index 6812740aa2..b81677b976 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md @@ -6,7 +6,7 @@ # Class: ChatApi -Defined in: [src/api.ts:62](../src/api.ts#L62) +Defined in: [src/api.ts:97](../src/api.ts#L97) Main API class for interacting with the chat core library. @@ -16,7 +16,7 @@ Main API class for interacting with the chat core library. > `protected` **ctrl\_**: `bigint` \| `undefined` -Defined in: [src/api.ts:68](../src/api.ts#L68) +Defined in: [src/api.ts:103](../src/api.ts#L103) ## Accessors @@ -26,7 +26,7 @@ Defined in: [src/api.ts:68](../src/api.ts#L68) > **get** **ctrl**(): `bigint` -Defined in: [src/api.ts:295](../src/api.ts#L295) +Defined in: [src/api.ts:329](../src/api.ts#L329) Chat controller reference @@ -42,7 +42,7 @@ Chat controller reference > **get** **initialized**(): `boolean` -Defined in: [src/api.ts:281](../src/api.ts#L281) +Defined in: [src/api.ts:315](../src/api.ts#L315) Chat controller is initialized @@ -58,7 +58,7 @@ Chat controller is initialized > **get** **started**(): `boolean` -Defined in: [src/api.ts:288](../src/api.ts#L288) +Defined in: [src/api.ts:322](../src/api.ts#L322) Chat controller is started @@ -72,7 +72,7 @@ Chat controller is started > **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:697](../src/api.ts#L697) +Defined in: [src/api.ts:731](../src/api.ts#L731) Accept contact request. Network usage: interactive. @@ -93,7 +93,7 @@ Network usage: interactive. > **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:517](../src/api.ts#L517) +Defined in: [src/api.ts:551](../src/api.ts#L551) Accept group member. Requires Admin role. Network usage: background. @@ -122,7 +122,7 @@ Network usage: background. > **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:497](../src/api.ts#L497) +Defined in: [src/api.ts:531](../src/api.ts#L531) Add contact to group. Requires bot to have Admin role. Network usage: interactive. @@ -151,7 +151,7 @@ Network usage: interactive. > **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\> -Defined in: [src/api.ts:537](../src/api.ts#L537) +Defined in: [src/api.ts:571](../src/api.ts#L571) Block members. Requires Moderator role. Network usage: background. @@ -180,7 +180,7 @@ Network usage: background. > **apiCancelFile**(`fileId`): `Promise`\<`void`\> -Defined in: [src/api.ts:487](../src/api.ts#L487) +Defined in: [src/api.ts:521](../src/api.ts#L521) Cancel file. Network usage: background. @@ -201,7 +201,7 @@ Network usage: background. > **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:461](../src/api.ts#L461) +Defined in: [src/api.ts:495](../src/api.ts#L495) Add/remove message reaction. Network usage: background. @@ -238,7 +238,7 @@ Network usage: background. > **apiConnect**(`userId`, `incognito`, `preparedLink?`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:666](../src/api.ts#L666) +Defined in: [src/api.ts:700](../src/api.ts#L700) Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link Network usage: interactive. @@ -267,7 +267,7 @@ Network usage: interactive. > **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:675](../src/api.ts#L675) +Defined in: [src/api.ts:709](../src/api.ts#L709) Connect via SimpleX link as string in the active user profile. Network usage: interactive. @@ -288,7 +288,7 @@ Network usage: interactive. > **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> -Defined in: [src/api.ts:656](../src/api.ts#L656) +Defined in: [src/api.ts:690](../src/api.ts#L690) Determine SimpleX link type and if the bot is already connected via this link. Network usage: interactive. @@ -313,7 +313,7 @@ Network usage: interactive. > **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\> -Defined in: [src/api.ts:774](../src/api.ts#L774) +Defined in: [src/api.ts:849](../src/api.ts#L849) Create new user profile Network usage: no. @@ -334,7 +334,7 @@ Network usage: no. > **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\> -Defined in: [src/api.ts:597](../src/api.ts#L597) +Defined in: [src/api.ts:631](../src/api.ts#L631) Create group link. Network usage: interactive. @@ -359,7 +359,7 @@ Network usage: interactive. > **apiCreateLink**(`userId`): `Promise`\<`string`\> -Defined in: [src/api.ts:643](../src/api.ts#L643) +Defined in: [src/api.ts:677](../src/api.ts#L677) Create 1-time invitation link. Network usage: interactive. @@ -376,11 +376,37 @@ Network usage: interactive. *** +### apiCreateMemberContact() + +> **apiCreateMemberContact**(`groupId`, `groupMemberId`): `Promise`\<`Contact`\> + +Defined in: [src/api.ts:915](../src/api.ts#L915) + +Create a direct message contact with a group member. +Returns the created contact. +Network usage: interactive. + +#### Parameters + +##### groupId + +`number` + +##### groupMemberId + +`number` + +#### Returns + +`Promise`\<`Contact`\> + +*** + ### apiCreateUserAddress() > **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\> -Defined in: [src/api.ts:312](../src/api.ts#L312) +Defined in: [src/api.ts:346](../src/api.ts#L346) Create bot address. Network usage: interactive. @@ -399,9 +425,9 @@ Network usage: interactive. ### apiDeleteChat() -> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode`): `Promise`\<`void`\> +> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode?`): `Promise`\<`void`\> -Defined in: [src/api.ts:737](../src/api.ts#L737) +Defined in: [src/api.ts:771](../src/api.ts#L771) Delete chat. Network usage: background. @@ -416,7 +442,7 @@ Network usage: background. `number` -##### deleteMode +##### deleteMode? `ChatDeleteMode` = `...` @@ -430,7 +456,7 @@ Network usage: background. > **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:436](../src/api.ts#L436) +Defined in: [src/api.ts:470](../src/api.ts#L470) Delete message. Network usage: background. @@ -463,7 +489,7 @@ Network usage: background. > **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\> -Defined in: [src/api.ts:619](../src/api.ts#L619) +Defined in: [src/api.ts:653](../src/api.ts#L653) Delete group link. Network usage: background. @@ -484,7 +510,7 @@ Network usage: background. > **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:451](../src/api.ts#L451) +Defined in: [src/api.ts:485](../src/api.ts#L485) Moderate message. Requires Moderator role (and higher than message author's). Network usage: background. @@ -509,7 +535,7 @@ Network usage: background. > **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\> -Defined in: [src/api.ts:804](../src/api.ts#L804) +Defined in: [src/api.ts:879](../src/api.ts#L879) Delete user profile. Network usage: background. @@ -538,7 +564,7 @@ Network usage: background. > **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\> -Defined in: [src/api.ts:322](../src/api.ts#L322) +Defined in: [src/api.ts:356](../src/api.ts#L356) Deletes a user address. Network usage: background. @@ -559,7 +585,7 @@ Network usage: background. > **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\> -Defined in: [src/api.ts:754](../src/api.ts#L754) +Defined in: [src/api.ts:829](../src/api.ts#L829) Get active user profile Network usage: no. @@ -570,11 +596,40 @@ Network usage: no. *** +### apiGetChat() + +> **apiGetChat**(`chatType`, `chatId`, `count`): `Promise`\<`any`\> + +Defined in: [src/api.ts:819](../src/api.ts#L819) + +Get chat items. +Network usage: no. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### count + +`number` + +#### Returns + +`Promise`\<`any`\> + +*** + ### apiGetGroupLink() > **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\> -Defined in: [src/api.ts:628](../src/api.ts#L628) +Defined in: [src/api.ts:662](../src/api.ts#L662) Get group link. Network usage: no. @@ -595,7 +650,7 @@ Network usage: no. > **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\> -Defined in: [src/api.ts:634](../src/api.ts#L634) +Defined in: [src/api.ts:668](../src/api.ts#L668) #### Parameters @@ -613,7 +668,7 @@ Defined in: [src/api.ts:634](../src/api.ts#L634) > **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\> -Defined in: [src/api.ts:332](../src/api.ts#L332) +Defined in: [src/api.ts:366](../src/api.ts#L366) Get bot address and settings. Network usage: no. @@ -634,7 +689,7 @@ Network usage: no. > **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:507](../src/api.ts#L507) +Defined in: [src/api.ts:541](../src/api.ts#L541) Join group. Network usage: interactive. @@ -655,7 +710,7 @@ Network usage: interactive. > **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:557](../src/api.ts#L557) +Defined in: [src/api.ts:591](../src/api.ts#L591) Leave group. Network usage: background. @@ -676,7 +731,7 @@ Network usage: background. > **apiListContacts**(`userId`): `Promise`\<`Contact`[]\> -Defined in: [src/api.ts:717](../src/api.ts#L717) +Defined in: [src/api.ts:751](../src/api.ts#L751) Get contacts. Network usage: no. @@ -697,7 +752,7 @@ Network usage: no. > **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\> -Defined in: [src/api.ts:727](../src/api.ts#L727) +Defined in: [src/api.ts:761](../src/api.ts#L761) Get groups. Network usage: no. @@ -726,7 +781,7 @@ Network usage: no. > **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:567](../src/api.ts#L567) +Defined in: [src/api.ts:601](../src/api.ts#L601) Get group members. Network usage: no. @@ -747,7 +802,7 @@ Network usage: no. > **apiListUsers**(): `Promise`\<`UserInfo`[]\> -Defined in: [src/api.ts:784](../src/api.ts#L784) +Defined in: [src/api.ts:859](../src/api.ts#L859) Get all user profiles Network usage: no. @@ -762,7 +817,7 @@ Network usage: no. > **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:577](../src/api.ts#L577) +Defined in: [src/api.ts:611](../src/api.ts#L611) Create group. Network usage: no. @@ -787,7 +842,7 @@ Network usage: no. > **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\> -Defined in: [src/api.ts:477](../src/api.ts#L477) +Defined in: [src/api.ts:511](../src/api.ts#L511) Receive file. Network usage: no. @@ -808,7 +863,7 @@ Network usage: no. > **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\> -Defined in: [src/api.ts:707](../src/api.ts#L707) +Defined in: [src/api.ts:741](../src/api.ts#L741) Reject contact request. The user who sent the request is **not notified**. Network usage: no. @@ -827,9 +882,9 @@ Network usage: no. ### apiRemoveMembers() -> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages`): `Promise`\<`GroupMember`[]\> +> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages?`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:547](../src/api.ts#L547) +Defined in: [src/api.ts:581](../src/api.ts#L581) Remove members. Requires Admin role. Network usage: background. @@ -844,7 +899,7 @@ Network usage: background. `number`[] -##### withMessages +##### withMessages? `boolean` = `false` @@ -854,11 +909,37 @@ Network usage: background. *** +### apiSendMemberContactInvitation() + +> **apiSendMemberContactInvitation**(`contactId`, `message?`): `Promise`\<`Contact`\> + +Defined in: [src/api.ts:926](../src/api.ts#L926) + +Send a direct message invitation to a group member contact. +The contact must have been created with [apiCreateMemberContact](#apicreatemembercontact). +Network usage: interactive. + +#### Parameters + +##### contactId + +`number` + +##### message? + +`string` \| `MsgContent` + +#### Returns + +`Promise`\<`Contact`\> + +*** + ### apiSendMessages() -> **apiSendMessages**(`chat`, `messages`, `liveMessage`): `Promise`\<`AChatItem`[]\> +> **apiSendMessages**(`chat`, `messages`, `liveMessage?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:381](../src/api.ts#L381) +Defined in: [src/api.ts:415](../src/api.ts#L415) Send messages. Network usage: background. @@ -867,13 +948,13 @@ Network usage: background. ##### chat -`ChatInfo` | `ChatRef` | \[`ChatType`, `number`\] +`ChatInfo` \| `ChatRef` \| \[`ChatType`, `number`\] ##### messages `ComposedMessage`[] -##### liveMessage +##### liveMessage? `boolean` = `false` @@ -887,7 +968,7 @@ Network usage: background. > **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:403](../src/api.ts#L403) +Defined in: [src/api.ts:437](../src/api.ts#L437) Send text message. Network usage: background. @@ -896,7 +977,7 @@ Network usage: background. ##### chat -`ChatInfo` | `ChatRef` | \[`ChatType`, `number`\] +`ChatInfo` \| `ChatRef` \| \[`ChatType`, `number`\] ##### text @@ -916,7 +997,7 @@ Network usage: background. > **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:411](../src/api.ts#L411) +Defined in: [src/api.ts:445](../src/api.ts#L445) Send text message in reply to received message. Network usage: background. @@ -941,7 +1022,7 @@ Network usage: background. > **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\> -Defined in: [src/api.ts:794](../src/api.ts#L794) +Defined in: [src/api.ts:869](../src/api.ts#L869) Set active user profile Network usage: no. @@ -966,7 +1047,7 @@ Network usage: no. > **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\> -Defined in: [src/api.ts:364](../src/api.ts#L364) +Defined in: [src/api.ts:398](../src/api.ts#L398) Set bot address settings. Network usage: interactive. @@ -987,11 +1068,61 @@ Network usage: interactive. *** +### apiSetAutoAcceptMemberContacts() + +> **apiSetAutoAcceptMemberContacts**(`userId`, `onOff`): `Promise`\<`void`\> + +Defined in: [src/api.ts:808](../src/api.ts#L808) + +Set auto-accept member contacts. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### onOff + +`boolean` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiSetContactCustomData() + +> **apiSetContactCustomData**(`contactId`, `customData?`): `Promise`\<`void`\> + +Defined in: [src/api.ts:798](../src/api.ts#L798) + +Set contact custom data. +Network usage: no. + +#### Parameters + +##### contactId + +`number` + +##### customData? + +`object` + +#### Returns + +`Promise`\<`void`\> + +*** + ### apiSetContactPrefs() > **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\> -Defined in: [src/api.ts:830](../src/api.ts#L830) +Defined in: [src/api.ts:905](../src/api.ts#L905) Configure chat preference overrides for the contact. Network usage: background. @@ -1012,11 +1143,36 @@ Network usage: background. *** +### apiSetGroupCustomData() + +> **apiSetGroupCustomData**(`groupId`, `customData?`): `Promise`\<`void`\> + +Defined in: [src/api.ts:788](../src/api.ts#L788) + +Set group custom data. +Network usage: no. + +#### Parameters + +##### groupId + +`number` + +##### customData? + +`object` + +#### Returns + +`Promise`\<`void`\> + +*** + ### apiSetGroupLinkMemberRole() > **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:610](../src/api.ts#L610) +Defined in: [src/api.ts:644](../src/api.ts#L644) Set member role for group link. Network usage: no. @@ -1041,7 +1197,7 @@ Network usage: no. > **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:527](../src/api.ts#L527) +Defined in: [src/api.ts:561](../src/api.ts#L561) Set members role. Requires Admin role. Network usage: background. @@ -1070,7 +1226,7 @@ Network usage: background. > **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\> -Defined in: [src/api.ts:350](../src/api.ts#L350) +Defined in: [src/api.ts:384](../src/api.ts#L384) Add address to bot profile. Network usage: interactive. @@ -1095,7 +1251,7 @@ Network usage: interactive. > **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage`): `Promise`\<`ChatItem`\> -Defined in: [src/api.ts:419](../src/api.ts#L419) +Defined in: [src/api.ts:453](../src/api.ts#L453) Update message. Network usage: background. @@ -1132,7 +1288,7 @@ Network usage: background. > **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:587](../src/api.ts#L587) +Defined in: [src/api.ts:621](../src/api.ts#L621) Update group profile. Network usage: background. @@ -1157,7 +1313,7 @@ Network usage: background. > **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\> -Defined in: [src/api.ts:814](../src/api.ts#L814) +Defined in: [src/api.ts:889](../src/api.ts#L889) Update user profile. Network usage: background. @@ -1182,7 +1338,7 @@ Network usage: background. > **close**(): `Promise`\<`void`\> -Defined in: [src/api.ts:114](../src/api.ts#L114) +Defined in: [src/api.ts:148](../src/api.ts#L148) Close chat database. Usually doesn't need to be called in chat bots. @@ -1195,9 +1351,9 @@ Usually doesn't need to be called in chat bots. ### off() -> **off**\<`K`\>(`event`, `subscriber`): `void` +> **off**\<`K`\>(`event`, `subscriber?`): `void` -Defined in: [src/api.ts:253](../src/api.ts#L253) +Defined in: [src/api.ts:287](../src/api.ts#L287) Unsubscribe all or a specific handler from a specific event. @@ -1215,12 +1371,12 @@ Unsubscribe all or a specific handler from a specific event. The event type to unsubscribe from. -##### subscriber +##### subscriber? + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> \| `undefined` An optional subscriber function for the event. -[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> | `undefined` - #### Returns `void` @@ -1229,20 +1385,20 @@ An optional subscriber function for the event. ### offAny() -> **offAny**(`receiver`): `void` +> **offAny**(`receiver?`): `void` -Defined in: [src/api.ts:269](../src/api.ts#L269) +Defined in: [src/api.ts:303](../src/api.ts#L303) Unsubscribe all or a specific handler from any events. #### Parameters -##### receiver +##### receiver? + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> \| `undefined` An optional subscriber function for the event. -[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> | `undefined` - #### Returns `void` @@ -1255,7 +1411,7 @@ An optional subscriber function for the event. > **on**\<`K`\>(`subscribers`): `void` -Defined in: [src/api.ts:163](../src/api.ts#L163) +Defined in: [src/api.ts:197](../src/api.ts#L197) Subscribe multiple event handlers at once. @@ -1285,7 +1441,7 @@ If the same function is subscribed to event. > **on**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:171](../src/api.ts#L171) +Defined in: [src/api.ts:205](../src/api.ts#L205) Subscribe a handler to a specific event. @@ -1323,7 +1479,7 @@ If the same function is subscribed to event. > **onAny**(`receiver`): `void` -Defined in: [src/api.ts:194](../src/api.ts#L194) +Defined in: [src/api.ts:228](../src/api.ts#L228) Subscribe a handler to any event. @@ -1349,7 +1505,7 @@ If the same function is subscribed to event. > **once**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:205](../src/api.ts#L205) +Defined in: [src/api.ts:239](../src/api.ts#L239) Subscribe a handler to a specific event to be delivered one time. @@ -1385,13 +1541,13 @@ If the same function is subscribed to event. ### recvChatEvent() -> **recvChatEvent**(`wait`): `Promise`\<`ChatEvent` \| `undefined`\> +> **recvChatEvent**(`wait?`): `Promise`\<`ChatEvent` \| `undefined`\> -Defined in: [src/api.ts:304](../src/api.ts#L304) +Defined in: [src/api.ts:338](../src/api.ts#L338) #### Parameters -##### wait +##### wait? `number` = `5_000_000` @@ -1405,7 +1561,7 @@ Defined in: [src/api.ts:304](../src/api.ts#L304) > **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\> -Defined in: [src/api.ts:300](../src/api.ts#L300) +Defined in: [src/api.ts:334](../src/api.ts#L334) #### Parameters @@ -1423,7 +1579,7 @@ Defined in: [src/api.ts:300](../src/api.ts#L300) > **startChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:88](../src/api.ts#L88) +Defined in: [src/api.ts:122](../src/api.ts#L122) Start chat controller. Must be called with the existing user profile. @@ -1437,7 +1593,7 @@ Start chat controller. Must be called with the existing user profile. > **stopChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:102](../src/api.ts#L102) +Defined in: [src/api.ts:136](../src/api.ts#L136) Stop chat controller. Must be called before closing the database. @@ -1453,9 +1609,9 @@ Usually doesn't need to be called in chat bots. #### Call Signature -> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:213](../src/api.ts#L213) +Defined in: [src/api.ts:247](../src/api.ts#L247) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1474,13 +1630,13 @@ Returns `undefined` on timeout if specified. ##### Returns -`Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +`Promise`\<`ChatEvent` & `object`\> #### Call Signature -> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:214](../src/api.ts#L214) +Defined in: [src/api.ts:248](../src/api.ts#L248) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1499,17 +1655,17 @@ Returns `undefined` on timeout if specified. ###### predicate -(`event`) => `boolean` | `undefined` +((`event`) => `boolean`) \| `undefined` ##### Returns -`Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +`Promise`\<`ChatEvent` & `object`\> #### Call Signature > **wait**\<`K`\>(`event`, `timeout`): `Promise`\ -Defined in: [src/api.ts:215](../src/api.ts#L215) +Defined in: [src/api.ts:249](../src/api.ts#L249) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1538,7 +1694,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\ -Defined in: [src/api.ts:216](../src/api.ts#L216) +Defined in: [src/api.ts:250](../src/api.ts#L250) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1557,7 +1713,7 @@ Returns `undefined` on timeout if specified. ###### predicate -(`event`) => `boolean` | `undefined` +((`event`) => `boolean`) \| `undefined` ###### timeout @@ -1571,25 +1727,19 @@ Returns `undefined` on timeout if specified. ### init() -> `static` **init**(`dbFilePrefix`, `dbKey?`, `confirm?`): `Promise`\<`ChatApi`\> +> `static` **init**(`db`, `confirm?`): `Promise`\<`ChatApi`\> -Defined in: [src/api.ts:76](../src/api.ts#L76) +Defined in: [src/api.ts:110](../src/api.ts#L110) Initializes the ChatApi. #### Parameters -##### dbFilePrefix +##### db -`string` +[`DbConfig`](api.TypeAlias.DbConfig.md) -File prefix for the database files. - -##### dbKey? - -`string` = `""` - -Database encryption key. +Database configuration (sqlite or postgres). ##### confirm? diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md index a4955cb3d9..5ec1d40540 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md @@ -74,7 +74,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6) ### stack? -> `optional` **stack**: `string` +> `optional` **stack?**: `string` Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) diff --git a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md index efb4a75e81..23c5e35326 100644 --- a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md @@ -14,7 +14,7 @@ Bot address settings. ### autoAccept? -> `optional` **autoAccept**: `boolean` +> `optional` **autoAccept?**: `boolean` Defined in: [src/api.ts:28](../src/api.ts#L28) @@ -30,7 +30,7 @@ true ### businessAddress? -> `optional` **businessAddress**: `boolean` +> `optional` **businessAddress?**: `boolean` Defined in: [src/api.ts:41](../src/api.ts#L41) @@ -47,7 +47,7 @@ false ### welcomeMessage? -> `optional` **welcomeMessage**: `string` \| `MsgContent` +> `optional` **welcomeMessage?**: `string` \| `MsgContent` Defined in: [src/api.ts:34](../src/api.ts#L34) diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md new file mode 100644 index 0000000000..7fe255327c --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md @@ -0,0 +1,64 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / DbConfig + +# Type Alias: DbConfig + +> **DbConfig** = \{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} \| \{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \} + +Defined in: [src/api.ts:65](../src/api.ts#L65) + +Database configuration. The native library is built against exactly one +backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this +type makes the caller state which one they are targeting so field names +can't lie about their meaning. + +## Union Members + +### Type Literal + +\{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} + +#### encryptionKey? + +> `optional` **encryptionKey?**: `string` + +Optional SQLCipher encryption key. Empty/omitted = unencrypted. + +#### filePrefix + +> **filePrefix**: `string` + +File prefix — two schema files are named `_chat.db` and `_agent.db`. + +#### type + +> **type**: `"sqlite"` + +SQLite backend (default). + +*** + +### Type Literal + +\{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \} + +#### connectionString + +> **connectionString**: `string` + +PostgreSQL connection string (e.g. `postgres://user:pass@host/db`). + +#### schemaPrefix? + +> `optional` **schemaPrefix?**: `string` + +Schema prefix used to namespace tables. Defaults to `"simplex_v1"` when omitted. + +#### type + +> **type**: `"postgres"` + +PostgreSQL backend (Linux x86_64 only, libpq5 required). diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md index 6197befc8a..bf40b3165c 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md @@ -4,7 +4,7 @@ [simplex-chat](README.md) / [api](Namespace.api.md) / EventSubscriberFunc -# Type Alias: EventSubscriberFunc()\ +# Type Alias: EventSubscriberFunc\ > **EventSubscriberFunc**\<`K`\> = (`event`) => `void` \| `Promise`\<`void`\> @@ -20,7 +20,7 @@ Defined in: [src/api.ts:50](../src/api.ts#L50) ### event -`ChatEvent` & \{ `type`: `K`; \} +`ChatEvent` & `object` ## Returns diff --git a/packages/simplex-chat-nodejs/docs/bot.Function.run.md b/packages/simplex-chat-nodejs/docs/bot.Function.run.md index bc31ad01a8..3c33e6c7d4 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Function.run.md +++ b/packages/simplex-chat-nodejs/docs/bot.Function.run.md @@ -8,7 +8,7 @@ > **run**(`__namedParameters`): `Promise`\<\[[`ChatApi`](api.Class.ChatApi.md), `User`, `UserContactLink` \| `undefined`\]\> -Defined in: [src/bot.ts:49](../src/bot.ts#L49) +Defined in: [src/bot.ts:47](../src/bot.ts#L47) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md index 0951c5a129..4624b1608b 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md @@ -6,43 +6,43 @@ # Interface: BotConfig -Defined in: [src/bot.ts:37](../src/bot.ts#L37) +Defined in: [src/bot.ts:35](../src/bot.ts#L35) ## Properties ### dbOpts -> **dbOpts**: [`BotDbOpts`](bot.Interface.BotDbOpts.md) +> **dbOpts**: [`BotDbOpts`](bot.TypeAlias.BotDbOpts.md) -Defined in: [src/bot.ts:39](../src/bot.ts#L39) +Defined in: [src/bot.ts:37](../src/bot.ts#L37) *** ### events? -> `optional` **events**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md) +> `optional` **events?**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md) -Defined in: [src/bot.ts:46](../src/bot.ts#L46) +Defined in: [src/bot.ts:44](../src/bot.ts#L44) *** ### onCommands? -> `optional` **onCommands**: \{\[`key`: `string`\]: (`chatItem`, `command`) => `void` \| `Promise`\<`void`\> \| `undefined`; \} +> `optional` **onCommands?**: `object` -Defined in: [src/bot.ts:43](../src/bot.ts#L43) +Defined in: [src/bot.ts:41](../src/bot.ts#L41) #### Index Signature -\[`key`: `string`\]: (`chatItem`, `command`) => `void` \| `Promise`\<`void`\> \| `undefined` +\[`key`: `string`\]: ((`chatItem`, `command`) => `void` \| `Promise`\<`void`\>) \| `undefined` *** -### onMessage()? +### onMessage? -> `optional` **onMessage**: (`chatItem`, `content`) => `void` \| `Promise`\<`void`\> +> `optional` **onMessage?**: (`chatItem`, `content`) => `void` \| `Promise`\<`void`\> -Defined in: [src/bot.ts:41](../src/bot.ts#L41) +Defined in: [src/bot.ts:39](../src/bot.ts#L39) #### Parameters @@ -64,7 +64,7 @@ Defined in: [src/bot.ts:41](../src/bot.ts#L41) > **options**: [`BotOptions`](bot.Interface.BotOptions.md) -Defined in: [src/bot.ts:40](../src/bot.ts#L40) +Defined in: [src/bot.ts:38](../src/bot.ts#L38) *** @@ -72,4 +72,4 @@ Defined in: [src/bot.ts:40](../src/bot.ts#L40) > **profile**: `Profile` -Defined in: [src/bot.ts:38](../src/bot.ts#L38) +Defined in: [src/bot.ts:36](../src/bot.ts#L36) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md deleted file mode 100644 index 7a9f113f6a..0000000000 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md +++ /dev/null @@ -1,33 +0,0 @@ -[**simplex-chat**](README.md) - -*** - -[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotDbOpts - -# Interface: BotDbOpts - -Defined in: [src/bot.ts:7](../src/bot.ts#L7) - -## Properties - -### confirmMigrations? - -> `optional` **confirmMigrations**: [`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) - -Defined in: [src/bot.ts:10](../src/bot.ts#L10) - -*** - -### dbFilePrefix - -> **dbFilePrefix**: `string` - -Defined in: [src/bot.ts:8](../src/bot.ts#L8) - -*** - -### dbKey? - -> `optional` **dbKey**: `string` - -Defined in: [src/bot.ts:9](../src/bot.ts#L9) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md index 44d4380e5a..eee56b879a 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md @@ -6,76 +6,76 @@ # Interface: BotOptions -Defined in: [src/bot.ts:13](../src/bot.ts#L13) +Defined in: [src/bot.ts:11](../src/bot.ts#L11) ## Properties ### addressSettings? -> `optional` **addressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) - -Defined in: [src/bot.ts:17](../src/bot.ts#L17) - -*** - -### allowFiles? - -> `optional` **allowFiles**: `boolean` - -Defined in: [src/bot.ts:18](../src/bot.ts#L18) - -*** - -### commands? - -> `optional` **commands**: `ChatBotCommand`[] - -Defined in: [src/bot.ts:19](../src/bot.ts#L19) - -*** - -### createAddress? - -> `optional` **createAddress**: `boolean` - -Defined in: [src/bot.ts:14](../src/bot.ts#L14) - -*** - -### logContacts? - -> `optional` **logContacts**: `boolean` - -Defined in: [src/bot.ts:21](../src/bot.ts#L21) - -*** - -### logNetwork? - -> `optional` **logNetwork**: `boolean` - -Defined in: [src/bot.ts:22](../src/bot.ts#L22) - -*** - -### updateAddress? - -> `optional` **updateAddress**: `boolean` +> `optional` **addressSettings?**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) Defined in: [src/bot.ts:15](../src/bot.ts#L15) *** -### updateProfile? +### allowFiles? -> `optional` **updateProfile**: `boolean` +> `optional` **allowFiles?**: `boolean` Defined in: [src/bot.ts:16](../src/bot.ts#L16) *** -### useBotProfile? +### commands? -> `optional` **useBotProfile**: `boolean` +> `optional` **commands?**: `ChatBotCommand`[] + +Defined in: [src/bot.ts:17](../src/bot.ts#L17) + +*** + +### createAddress? + +> `optional` **createAddress?**: `boolean` + +Defined in: [src/bot.ts:12](../src/bot.ts#L12) + +*** + +### logContacts? + +> `optional` **logContacts?**: `boolean` + +Defined in: [src/bot.ts:19](../src/bot.ts#L19) + +*** + +### logNetwork? + +> `optional` **logNetwork?**: `boolean` Defined in: [src/bot.ts:20](../src/bot.ts#L20) + +*** + +### updateAddress? + +> `optional` **updateAddress?**: `boolean` + +Defined in: [src/bot.ts:13](../src/bot.ts#L13) + +*** + +### updateProfile? + +> `optional` **updateProfile?**: `boolean` + +Defined in: [src/bot.ts:14](../src/bot.ts#L14) + +*** + +### useBotProfile? + +> `optional` **useBotProfile?**: `boolean` + +Defined in: [src/bot.ts:18](../src/bot.ts#L18) diff --git a/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md b/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md new file mode 100644 index 0000000000..b035f41355 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md @@ -0,0 +1,17 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotDbOpts + +# Type Alias: BotDbOpts + +> **BotDbOpts** = [`DbConfig`](api.TypeAlias.DbConfig.md) & `object` + +Defined in: [src/bot.ts:7](../src/bot.ts#L7) + +## Type Declaration + +### confirmMigrations? + +> `optional` **confirmMigrations?**: [`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md index c6082f2985..5bd0722f0c 100644 --- a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md @@ -16,7 +16,7 @@ Defined in: [src/core.ts:92](../src/core.ts#L92) ### Constructor -> **new ChatAPIError**(`message`, `chatError`): `ChatAPIError` +> **new ChatAPIError**(`message`, `chatError?`): `ChatAPIError` Defined in: [src/core.ts:93](../src/core.ts#L93) @@ -26,9 +26,9 @@ Defined in: [src/core.ts:93](../src/core.ts#L93) `string` -##### chatError +##### chatError? -`ChatError` | `undefined` +`ChatError` \| `undefined` #### Returns @@ -74,7 +74,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ ### stack? -> `optional` **stack**: `string` +> `optional` **stack?**: `string` Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md index eff5123fb0..0feceae4fd 100644 --- a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md @@ -74,7 +74,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ ### stack? -> `optional` **stack**: `string` +> `optional` **stack?**: `string` Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md index 906ef3ec3e..8d18997ec4 100644 --- a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md +++ b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md @@ -18,7 +18,7 @@ Defined in: [src/core.ts:87](../src/core.ts#L87) ### error? -> `optional` **error**: `ChatError` +> `optional` **error?**: `ChatError` Defined in: [src/core.ts:89](../src/core.ts#L89) @@ -26,6 +26,6 @@ Defined in: [src/core.ts:89](../src/core.ts#L89) ### result? -> `optional` **result**: `R` +> `optional` **result?**: `R` Defined in: [src/core.ts:88](../src/core.ts#L88) diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js index 16d0678b64..8899e9bf15 100644 --- a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js +++ b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js @@ -2,7 +2,7 @@ const {bot} = await import("../dist/index.js") const [chat, _user, _address] = await bot.run({ profile: {displayName: "Squaring bot example", fullName: ""}, - dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"}, options: { addressSettings: {welcomeMessage: "Send a number, I will square it."}, }, diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot.ts b/packages/simplex-chat-nodejs/examples/squaring-bot.ts index 682e7b887a..dbb58d90dc 100644 --- a/packages/simplex-chat-nodejs/examples/squaring-bot.ts +++ b/packages/simplex-chat-nodejs/examples/squaring-bot.ts @@ -5,7 +5,7 @@ import {bot, util} from "../dist" const welcomeMessage = "Hello! I am a simple squaring bot.\n\nIf you send me a number, I will calculate its square." const [chat, _user, _address] = await bot.run({ profile: {displayName: "Squaring bot example", fullName: ""}, - dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"}, options: { addressSettings: {autoAccept: true, welcomeMessage, businessAddress: false}, commands: [ // commands to show in client UI diff --git a/packages/simplex-chat-nodejs/package.json b/packages/simplex-chat-nodejs/package.json index 21e00e7399..05f9c0e7d7 100644 --- a/packages/simplex-chat-nodejs/package.json +++ b/packages/simplex-chat-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "simplex-chat", - "version": "6.5.0-beta.4.5", + "version": "6.5.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -24,7 +24,7 @@ "docs": "typedoc" }, "dependencies": { - "@simplex-chat/types": "^0.4.0", + "@simplex-chat/types": "^0.5.0", "extract-zip": "^2.0.1", "fast-deep-equal": "^3.1.3", "node-addon-api": "^8.5.0" diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index 8bc56db41c..f1337e6753 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -56,6 +56,41 @@ interface EventSubscriber { once: boolean } +/** + * Database configuration. The native library is built against exactly one + * backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this + * type makes the caller state which one they are targeting so field names + * can't lie about their meaning. + */ +export type DbConfig = + | { + /** SQLite backend (default). */ + type: "sqlite" + /** File prefix — two schema files are named `_chat.db` and `_agent.db`. */ + filePrefix: string + /** Optional SQLCipher encryption key. Empty/omitted = unencrypted. */ + encryptionKey?: string + } + | { + /** PostgreSQL backend (Linux x86_64 only, libpq5 required). */ + type: "postgres" + /** Schema prefix used to namespace tables. Defaults to `"simplex_v1"` when omitted. */ + schemaPrefix?: string + /** PostgreSQL connection string (e.g. `postgres://user:pass@host/db`). */ + connectionString: string + } + +function dbConfigToMigrateArgs(db: DbConfig): [string, string] { + switch (db.type) { + case "sqlite": + return [db.filePrefix, db.encryptionKey ?? ""] + case "postgres": + return [db.schemaPrefix ?? "", db.connectionString] + default: + throw new Error(`Invalid DbConfig: ${JSON.stringify(db satisfies never)}`) + } +} + /** * Main API class for interacting with the chat core library. */ @@ -64,21 +99,20 @@ export class ChatApi { private eventsLoop: Promise | undefined = undefined private subscribers: {[K in CEvt.Tag]?: EventSubscriber[]} = {} private receivers: EventSubscriberFunc[] = [] - + private constructor(protected ctrl_: bigint | undefined) {} /** * Initializes the ChatApi. - * @param {string} dbFilePrefix - File prefix for the database files. - * @param {string} [dbKey=""] - Database encryption key. + * @param {DbConfig} db - Database configuration (sqlite or postgres). * @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode. */ static async init( - dbFilePrefix: string, - dbKey: string = "", + db: DbConfig, confirm = core.MigrationConfirmation.YesUp ): Promise { - const ctrl = await core.chatMigrateInit(dbFilePrefix, dbKey, confirm) + const [path, key] = dbConfigToMigrateArgs(db) + const ctrl = await core.chatMigrateInit(path, key, confirm) return new ChatApi(ctrl) } @@ -654,7 +688,7 @@ export class ChatApi { * Network usage: interactive. */ async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> { - const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink})) + const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink, resolveKnown: false})) if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink] throw new ChatCommandError("error getting connect plan", r) } diff --git a/packages/simplex-chat-nodejs/src/bot.ts b/packages/simplex-chat-nodejs/src/bot.ts index 95a0c13d96..f6cb753d27 100644 --- a/packages/simplex-chat-nodejs/src/bot.ts +++ b/packages/simplex-chat-nodejs/src/bot.ts @@ -4,9 +4,7 @@ import * as core from "./core" import * as util from "./util" import equal = require("fast-deep-equal") -export interface BotDbOpts { - dbFilePrefix: string // two schema files will be named _chat.db and _agent.db - dbKey?: string +export type BotDbOpts = api.DbConfig & { confirmMigrations?: core.MigrationConfirmation } @@ -47,7 +45,7 @@ export interface BotConfig { } export async function run({profile, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> { - const bot = await api.ChatApi.init(dbOpts.dbFilePrefix, dbOpts.dbKey || "", dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp) + const bot = await api.ChatApi.init(dbOpts, dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp) const opts = fullOptions(options) if (onMessage) subscribeMessages(bot, onMessage) if (Object.keys(onCommands).length > 0) subscribeCommands(bot, onCommands) diff --git a/packages/simplex-chat-nodejs/src/download-libs.js b/packages/simplex-chat-nodejs/src/download-libs.js index 25d4127ea4..25d6cc8a85 100644 --- a/packages/simplex-chat-nodejs/src/download-libs.js +++ b/packages/simplex-chat-nodejs/src/download-libs.js @@ -4,7 +4,19 @@ const path = require('path'); const extract = require('extract-zip'); const GITHUB_REPO = 'simplex-chat/simplex-chat-libs'; -const RELEASE_TAG = 'v6.5.0-beta.9'; +const RELEASE_TAG = 'v6.5.0'; +const BACKEND = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || 'sqlite').toLowerCase(); + +if (BACKEND !== 'sqlite' && BACKEND !== 'postgres') { + console.error(`✗ Invalid SIMPLEX_BACKEND: "${BACKEND}". Must be "sqlite" or "postgres".`); + process.exit(1); +} + +if (BACKEND === 'postgres' && (process.platform !== 'linux' || process.arch !== 'x64')) { + console.error(`✗ SIMPLEX_BACKEND=postgres is only supported on Linux x86_64.`); + process.exit(1); +} + const ROOT_DIR = process.cwd(); // Root of the package being installed const LIBS_DIR = path.join(ROOT_DIR, 'libs') const INSTALLED_FILE = path.join(LIBS_DIR, 'installed.txt'); @@ -56,11 +68,12 @@ function isAlreadyInstalled() { try { const installedVersion = fs.readFileSync(INSTALLED_FILE, 'utf-8').trim(); - if (installedVersion === RELEASE_TAG) { - console.log(`✓ Libraries version ${RELEASE_TAG} already installed`); + const expectedVersion = `${RELEASE_TAG}:${BACKEND}`; + if (installedVersion === expectedVersion) { + console.log(`✓ Libraries version ${RELEASE_TAG}:${BACKEND} already installed`); return true; } else { - console.log(`Version mismatch: installed ${installedVersion}, need ${RELEASE_TAG}`); + console.log(`Version mismatch: installed ${installedVersion}, need ${expectedVersion}`); cleanLibsDirectory(); return false; } @@ -79,12 +92,14 @@ async function install() { const { platformName, archName } = getPlatformInfo(); const repoName = GITHUB_REPO.split('/')[1]; - const zipFilename = `${repoName}-${platformName}-${archName}.zip`; + const backendSuffix = BACKEND === 'postgres' ? '-postgres' : ''; + const zipFilename = `${repoName}-${platformName}-${archName}${backendSuffix}.zip`; const ZIP_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}/${zipFilename}`; const ZIP_PATH = path.join(ROOT_DIR, zipFilename); const TEMP_EXTRACT_DIR = path.join(ROOT_DIR, '.temp-extract'); console.log(`Detected: ${platformName} ${archName}`); + console.log(`Backend: ${BACKEND}`); console.log(`Downloading: ${zipFilename}`); // Create libs directory @@ -124,8 +139,8 @@ async function install() { } // Write installed.txt with version - fs.writeFileSync(INSTALLED_FILE, RELEASE_TAG, 'utf-8'); - console.log(`✓ Wrote version ${RELEASE_TAG} to installed.txt`); + fs.writeFileSync(INSTALLED_FILE, `${RELEASE_TAG}:${BACKEND}`, 'utf-8'); + console.log(`✓ Wrote version ${RELEASE_TAG}:${BACKEND} to installed.txt`); // Cleanup fs.rmSync(TEMP_EXTRACT_DIR, { recursive: true, force: true }); diff --git a/packages/simplex-chat-nodejs/tests/api.test.ts b/packages/simplex-chat-nodejs/tests/api.test.ts index 7bc1a89b86..99d511371c 100644 --- a/packages/simplex-chat-nodejs/tests/api.test.ts +++ b/packages/simplex-chat-nodejs/tests/api.test.ts @@ -15,8 +15,8 @@ describe("API tests (use preset servers)", () => { it("should send/receive message", async () => { // create users and start chat controllers - const alice = await api.ChatApi.init(alicePath) - const bob = await api.ChatApi.init(bobPath) + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath}) const servers: string[] = [] let eventCount = 0 alice.on("hostConnected" as CEvt.Tag, async ({transportHost}: any) => { servers.push(transportHost) }) @@ -67,10 +67,10 @@ describe("API tests (use preset servers)", () => { it("should create member contact and send invitation", async () => { // create 3 users and start chat controllers - const alice = await api.ChatApi.init(alicePath) - const bob = await api.ChatApi.init(bobPath) + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath}) const carolPath = path.join(tmpDir, "carol") - const carol = await api.ChatApi.init(carolPath) + const carol = await api.ChatApi.init({type: "sqlite", filePrefix: carolPath}) const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) await bob.apiCreateActiveUser({displayName: "bob", fullName: ""}) await carol.apiCreateActiveUser({displayName: "carol", fullName: ""}) diff --git a/packages/simplex-chat-nodejs/tests/bot.test.ts b/packages/simplex-chat-nodejs/tests/bot.test.ts index b1fd9d0186..5a7faa663f 100644 --- a/packages/simplex-chat-nodejs/tests/bot.test.ts +++ b/packages/simplex-chat-nodejs/tests/bot.test.ts @@ -18,7 +18,7 @@ describe("Bot tests (use preset servers)", () => { // run bot const [chat, botUser, botAddress] = await bot.run({ profile: {displayName: "Squaring bot", fullName: ""}, - dbOpts: {dbFilePrefix: botPath, dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: botPath}, options: { addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, }, @@ -30,7 +30,7 @@ describe("Bot tests (use preset servers)", () => { }) assert(typeof botAddress === "object") // create user - const alice = await api.ChatApi.init(alicePath) + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) await alice.startChat() // connect to bot diff --git a/plans/2026-04-06-onboarding-cards-compose.md b/plans/2026-04-06-onboarding-cards-compose.md index 00df159eee..648f9e1d81 100644 --- a/plans/2026-04-06-onboarding-cards-compose.md +++ b/plans/2026-04-06-onboarding-cards-compose.md @@ -476,7 +476,7 @@ Screen 2: Talk to someone Let someone connect to you Connect via link or QR code -Connect with someone +Create your link Invite someone privately A link for one person to connect Create your public address diff --git a/plans/2026-04-06-onboarding-cards-ios.md b/plans/2026-04-06-onboarding-cards-ios.md index f20936ea05..d9c310a93e 100644 --- a/plans/2026-04-06-onboarding-cards-ios.md +++ b/plans/2026-04-06-onboarding-cards-ios.md @@ -16,7 +16,7 @@ Each page has a header area containing: - **Back button area:** fixed height 44pt. Screen 1: empty space. Screen 2: "< Back" button left-aligned. - **Title:** centered, largeTitle font, bold, single line, shrinks to 75% minimum scale factor. - Screen 1 title: "Talk to someone" -- Screen 2 title: "Connect with someone" +- Screen 2 title: "Create your link" **Portrait:** back button area and title are two separate rows (VStack). **Landscape:** back button and title share one row (ZStack — back button leading, title centered). No separate back button row — saves vertical space. @@ -164,7 +164,7 @@ Auto-dismiss: when first real conversation appears, set `addressCreationCardShow - "Talk to someone" - "Let someone connect to you" - "Connect via link or QR code" -- "Connect with someone" +- "Create your link" - "Invite someone privately" - "A link for one person to connect" - "Create your public address" @@ -303,7 +303,7 @@ struct ConnectWithSomeoneView: View { ``` Same VStack layout as Screen 1, with these differences: -- Title: "Connect with someone" +- Title: "Create your link" - Card 1: imageName `"card-invite-someone-privately-alpha"`, icon `"link"`, title "Invite someone privately", subtitle "A link for one person to connect" → sets `showInviteSomeone = true` - Card 2: imageName `"card-create-your-public-address-alpha"`, icon `"qrcode"`, title "Create your public address", subtitle "For anyone to reach you" → sets `showCreateAddress = true` @@ -415,7 +415,7 @@ No new user default needed. - "Talk to someone" - "Let someone connect to you" - "Connect via link or QR code" -- "Connect with someone" +- "Create your link" - "Invite someone privately" - "A link for one person to connect" - "Create your public address" diff --git a/plans/2026-04-19-directory-public-groups.md b/plans/2026-04-19-directory-public-groups.md new file mode 100644 index 0000000000..1b23234d14 --- /dev/null +++ b/plans/2026-04-19-directory-public-groups.md @@ -0,0 +1,324 @@ +# Directory Service — Public Group Registration via Chat Cards + +## Goal + +Enable directory registration of public groups (channels and future group types) via MCChat cards shared in DM with the bot. Replaces the admin-invitation flow with a signature-verified card flow. + +## Background + +### Current group registration flow +1. Owner invites bot as admin member +2. Bot joins, creates group link, asks owner to add link to welcome message +3. Owner updates profile with link → bot sends for admin approval +4. Admin approves → group listed + +This requires the bot to be admin. Public groups don't need this — they already have a public link, and ownership is proven via `ownerSig` on the MCChat card. + +### Public group identity +- `PublicGroupProfile {groupType :: GroupType, groupLink :: ShortLinkContact, publicGroupId :: B64UrlByteString}` +- `publicGroupId = sha256(rootKey)` — immutable identity +- `GroupType`: currently `GTChannel`, adding `GTGroup` for forward compatibility +- `GroupKeys {publicGroupId, groupRootKey, memberPrivKey}` — owner's signing keys +- `ownerId` in `LinkOwnerSig` = `B64UrlByteString (unMemberId memberId)` — the owner's MemberId bytes + +### ownerId-to-member mapping +- `LinkOwnerSig.ownerId = Just (B64UrlByteString unMemberId)` — same raw bytes as `MemberId` +- `createLinkOwnerMember` (called during `APIConnectPreparedGroup`, Commands.hs:2129) creates a member record with `memberRole = GROwner`, `memberStatus = GSMemUnknown`, `memberContactId = Nothing` +- `GroupMemberId` is available immediately after `APIConnectPreparedGroup` +- `getGroupMemberIdViaMemberId db user gInfo (MemberId ownerId)` looks up `GroupMemberId` from `MemberId` + +### Owner member activation +When a relay announces the pre-created `GSMemUnknown` member, `CEvtUnknownMemberAnnounced` fires (Subscriber.hs:2872, via `xGrpMemNew`). The member's profile and role are updated from the announcement's `MemberInfo` (via `updateUnknownMemberAnnounced`, Groups.hs:3010) — the role reflects the member's actual current role, not the pre-created `GROwner`. This event is not currently handled in directory Events.hs. + +### connectPlan and known groups +`apiConnectPlan` with `linkOwnerSig` returns: +- `GLPOk {groupSLinkData_, ownerVerification}` — new group +- `GLPKnown {groupInfo}` — bot already a member +- `GLPOwnLink` / `GLPConnectingProhibit` / `GLPConnectingConfirmReconnect` / `GLPNoRelays` + +**Gap**: For `GLPKnown`, `groupShortLinkPlan` short-circuits via `knownLinkPlans` — never resolves link data, never verifies signature. + +**Fix**: Add an optional parameter to `APIConnectPlan` (before `sig=`, since JSON must be last) that forces link data re-resolution even for known groups. With this parameter, `GLPKnown` includes `ownerVerification` and freshly loaded `groupSLinkData`. The loaded profile may differ from stored — the bot treats the server's current data as authoritative and updates its stored profile accordingly. + +**Future**: Add a signed version counter to link data to detect rollback attacks (malicious server serving old signed profiles). The bot would store the highest version seen and reject/flag version reductions. For now, the server is treated as authoritative. + +### Owner-contact association via APIConnectPreparedGroup +`createLinkOwnerMember` (called during `APIConnectPreparedGroup`) currently creates owner members with `memberContactId = Nothing`. Add an optional `(contactId, ownerId)` paired parameter to `APIConnectPreparedGroup`: when the link was received in a DM, pass the sender's `contactId` and the `ownerId` from `LinkOwnerSig`. The core sets `memberContactId` on the specific owner member whose `memberId` matches `ownerId`. + +This makes ALL existing directory event routing work: `DEContactRoleChanged`, `DEContactRemovedFromGroup`, `DEContactLeftGroup` all resolve via `memberContactId` — no new event types needed for owner tracking. + +Also benefits regular UI: when a user taps an owner's link in a DM, the contact association is created, improving the experience (e.g., showing the contact in the group member list). + +## Registration flow for public groups + +1. Owner taps "Share via chat" on their public group → sends MCChat card to bot in DM +2. Bot receives `CEvtNewChatItems` with `MCChat` content in direct chat → `DEChatLinkReceived` +3. Bot validates card (see validation matrix) +4. Bot calls `apiConnectPlan` with `connLink`, `linkOwnerSig`, and force-resolve flag +5. On `GLPOk` + `Verified`: bot replies "Joining {channel/group} {name}..." and joins via `APIPrepareGroup` then `APIConnectPreparedGroup` (passing owner's `contactId` and `ownerId`). On error: replies "Error joining {channel/group} {name}, please re-send the link!" (same pattern as existing group flow, Service.hs:368-370). +6. After `APIConnectPreparedGroup`, bot stores `dbOwnerMemberId` (via `getGroupMemberIdViaMemberId` — `createLinkOwnerMember` created the record during connect). Registration status: `GRSProposed`. +7. When `CEvtUnknownMemberAnnounced` fires for the owner member → `DEOwnerMemberAnnounced` → bot transitions to `GRSPendingApproval`, replies "Joined {channel/group} {name}. Registration is pending approval — it may take up to 48 hours.", sends to admins for approval +8. Admin approves → `GRSActive` + +## Scenario matrix: card received in DM + +### Event + +One event: `DEChatLinkReceived { contact :: Contact, chatItemId :: ChatItemId, chatLink :: MsgChatLink, ownerSig :: Maybe LinkOwnerSig }`. + +Handler validates and replies based on content. + +### Card validation (handler level) + +| Condition | Action | +|---|---| +| `chatLink` is not MCLGroup, or MCLGroup but no `publicGroup` in profile | Reply: "Only channels can be added to directory via link." | +| MCLGroup + publicGroup but `ownerSig` is `Nothing` | Reply: "To add a {channel/group} to directory you must be the owner." | +| MCLGroup + publicGroup + `ownerSig` is `Just` | Proceed to connectPlan | + +### connectPlan results + +| Plan result | ownerVerification | Action | +|---|---|---| +| `GLPOk` + sLinkData | `Verified` | Reply "Joining {channel/group} {name}...", join (with contactId + ownerId), register as `GRSProposed` | +| `GLPOk` + sLinkData | `Failed reason` | Reply: "Link signature verification failed: {reason}.\nYou must be the {channel/group} owner to register it." | +| `GLPOk` + sLinkData | `Nothing` | Reply: "Error: could not verify {channel/group} ownership. Please report it to directory admins." | +| `GLPOk` no sLinkData | — | Reply: "Error: no {channel/group} information available via the link." | +| `GLPKnown` | `Verified` | Bot already member — handle as re-registration (see below) | +| `GLPKnown` | `Failed reason` | Reply: "Link signature verification failed: {reason}.\nYou must be the {channel/group} owner to register it." | +| `GLPKnown` | `Nothing` | Reply: "Error: could not verify ownership." | +| `GLPConnectingProhibit` | — | Reply: "Already connecting to this {channel/group}." | +| `GLPConnectingConfirmReconnect` | — | Reply: "Already connecting to this {channel/group}." | +| `GLPOwnLink` | — | Log error. Reply: "Unexpected error. Please report it to directory admins." | +| `GLPNoRelays` | — | Reply: "{Channel/Group} has no active relays. Please try again later." | + +### Owner member activation after joining + +Bot is in `GRSProposed`. The pre-created owner member has `GSMemUnknown` status. When the relay announces this member, `CEvtUnknownMemberAnnounced` fires → mapped to `DEOwnerMemberAnnounced` in directory events. + +| Condition | Action | +|---|---| +| `CEvtUnknownMemberAnnounced` for member matching `dbOwnerMemberId`, announced role is `GROwner` | Transition to `GRSPendingApproval`, notify submitting contact, send for admin approval | +| `CEvtUnknownMemberAnnounced` for member matching `dbOwnerMemberId`, announced role < `GROwner` | Reply: "The signing key does not belong to a current owner. Registration cancelled." Set `GRSRemoved`. | +| Owner member never announced | Registration stays in `GRSProposed`. No timeout — manual cleanup via admin. | + +### Re-registration (GLPKnown — bot already member, signature verified at plan) + +With the `connectPlan` fix, `GLPKnown` now includes `ownerVerification` and fresh `groupSLinkData`. Only proceed if `Verified`. + +Bot extracts `ownerId`, looks up member via `getGroupMemberIdViaMemberId`, confirms `memberRole >= GROwner` AND `memberStatus` is active (not `GSMemUnknown`). The pre-created member has `GROwner` role from creation, so role alone is insufficient — the member must have been announced by a relay to confirm actual presence in the group. + +Look up existing `GroupReg` by `groupId`: + +| Existing registration | Ownership verified | Action | +|---|---|---| +| No GroupReg found | Yes | Create new registration as `GRSPendingApproval` | +| GroupReg exists, same owner contact | Yes | Handle based on current status (see status matrix) | +| GroupReg exists, different contact | Sender is verified owner AND previous registrant no longer owner (check `dbOwnerMemberId` member's current role) | Transfer: update `dbContactId` and `dbOwnerMemberId`, proceed as same-owner case | +| GroupReg exists, different contact | Sender is verified owner BUT previous registrant still owner | Reply: "This {channel/group} is registered by another owner." | +| GroupReg exists, different contact | Sender NOT verified owner | Reply: "You must be the {channel/group} owner to register it." Additionally: check if previous registrant (via `dbOwnerMemberId`) is still owner. If not → suspend (`GRSSuspendedBadRoles`). | + +### Re-registration by same owner — status matrix + +| Current status | Action | +|---|---| +| `GRSProposed` | Only if owner member is active (not `GSMemUnknown`): transition to `GRSPendingApproval`, send for approval. If still `GSMemUnknown`: reply "Waiting for owner to connect to the {channel/group}." | +| `GRSPendingConfirmation` | Transition to `GRSPendingApproval`, send for approval (only if previously registered via admin-invitation flow) | +| `GRSPendingUpdate` | Transition to `GRSPendingApproval`, send for approval (only if previously registered via admin-invitation flow) | +| `GRSPendingApproval n` | Check if profile changed (fresh profile from connectPlan vs bot's current DB). If yes: increment approval ID, re-send. If no: reply "Already pending approval." | +| `GRSActive` | Check if profile changed. If yes: transition to `GRSPendingApproval`, re-send. If no: reply "Already listed in the directory." | +| `GRSSuspended` | Reply: "{Channel/Group} is suspended by admin. Contact support." | +| `GRSSuspendedBadRoles` | Ownership re-verified at plan. Transition to `GRSPendingApproval`, send for approval. | +| `GRSRemoved` | Re-register as `GRSPendingApproval` | + +### Profile change detection + +For re-registration: compare the freshly loaded profile (from connectPlan's re-resolved `groupSLinkData`) against the group's current profile in the bot's database. + +For XGrpInfo updates: re-resolve the link via `apiConnectPlan` with `resolve=on`, compare freshly loaded link profile against bot's stored profile. + +Uses the same `sameProfile` comparison as existing group flow (Service.hs:491-494), extended with `publicGroup` field: `displayName`, `fullName`, `shortDescr`, `image`, `description`, `memberAdmission`, `publicGroup` — any difference triggers re-approval. The `publicGroup` field includes `groupLink` (ShortLinkContact), so link regeneration by the owner also triggers re-approval. + +## Profile updates via XGrpInfo (bot is subscriber) + +Bot receives `DEGroupUpdated` when any member updates the group profile. Works for subscribers. + +For public groups: skip "link in welcome message" check. First check if the profile actually changed using the same `sameProfile` comparison as for regular groups (`displayName`, `fullName`, `shortDescr`, `image`, `description`, `memberAdmission`). Only if changed, call `apiConnectPlan` with `resolve=on` to re-resolve the link data. Compare the resolved link profile against the bot's stored profile. + +Note: `xGrpInfo` (Subscriber.hs:3172) prevents `publicGroup` removal and `publicGroupId` changes for channels — these cases can never occur. The `groupLink` (ShortLinkContact) CAN change if the owner regenerates the link; the bot's DB is updated via XGrpInfo and subsequent re-resolution uses the current link. + +| Current status | Profile changed (link data vs stored) | Action | +|---|---|---| +| `GRSProposed` | Any | No action (waiting for owner activation) | +| `GRSPendingApproval n` | Yes | Increment approval ID, re-send for approval | +| `GRSPendingApproval n` | No | No action | +| `GRSActive` | Yes | Transition to `GRSPendingApproval`, notify owner, re-send | +| `GRSActive` | No | No action | +| `GRSSuspended` | Any | No action | +| `GRSSuspendedBadRoles` | Any | No action | +| `GRSRemoved` | Any | No action | + +## Owner tracking + +### Owner-contact association + +When the bot connects via `APIConnectPreparedGroup` with the submitting contact's `contactId` and `ownerId`, the core sets `memberContactId` on the specific pre-created owner member whose `memberId` matches `ownerId`. This makes all existing event routing work: `DEContactRoleChanged`, `DEContactRemovedFromGroup`, `DEContactLeftGroup` resolve via `memberContactId`. + +### Owner changes + +| Event | Detection | Action | +|---|---|---| +| Owner loses owner role | `DEContactRoleChanged` (works via `memberContactId` set at connect time) | Transition to `GRSSuspendedBadRoles`, notify | +| Owner leaves group | `DEContactLeftGroup` | Transition to `GRSRemoved`, notify, leave group | +| Owner removed from group | `DEContactRemovedFromGroup` | Transition to `GRSRemoved`, notify, leave group | +| Non-owner sends card, current registrant no longer owner | Re-registration flow detects stale ownership | Suspend (`GRSSuspendedBadRoles`). Non-owner's card also checked: if their `ownerId` resolves to a non-owner member, and the current registrant is also not owner → suspend. | +| New owner sends card, current registrant no longer owner | Re-registration flow, verified | Transfer registration | + +## Commands for public group registrations + +Bot is subscriber (not admin): +- `/filter` — Reply: "This command is not available for public groups." +- `/role` — Reply: "This command is not available for public groups." +- `/link` — Show `PublicGroupProfile.groupLink` with appropriate message. +- `/delete` — Remove registration, bot leaves group (`APILeaveGroup`). +- `/list` — Works as before, includes public group registrations. + +## De-registration + +| Event | Action | +|---|---| +| Owner sends `/delete ID:NAME` | Delete registration, reply confirmation, leave group | +| Bot removed (`DEServiceRemovedFromGroup`) | Set `GRSRemoved`, notify | +| Group deleted (`DEGroupDeleted`) | Set `GRSRemoved`, notify | +| Owner leaves (`DEContactLeftGroup`) | Set `GRSRemoved`, notify, leave group | +| Owner removed (`DEContactRemovedFromGroup`) | Set `GRSRemoved`, notify, leave group | +| Admin sends `/suspend ID:NAME` | Set `GRSSuspended`, notify, do NOT leave group | + +Bot leaves group only for public group registrations (regular groups preserve existing behavior). + +## Code changes + +### 1. GroupType — add GTGroup + +`Types.hs`: +```haskell +data GroupType = GTChannel | GTGroup | GTUnknown Text +``` + +### 2. connectPlan — force-resolve parameter + +Add optional parameter to `APIConnectPlan` (before `sig=`): `resolve=on`. When present, `groupShortLinkPlan` skips the `knownLinkPlans` shortcut and always resolves link data. `GLPKnown` extended with `ownerVerification` and `groupSLinkData_`: +```haskell +GLPKnown {groupInfo :: GroupInfo, ownerVerification :: Maybe OwnerVerification, groupSLinkData_ :: Maybe GroupShortLinkData} +``` + +Parser: `/_connect plan [resolve=on] [sig=]` + +### 3. APIConnectPreparedGroup — optional (contactId, ownerId) + +Add optional paired `(contactId, ownerId)` parameter to `APIConnectPreparedGroup`. When present, `createLinkOwnerMember` (called during connect, Commands.hs:2129) sets `memberContactId` on the specific owner member whose `memberId` matches the provided `ownerId`. + +Current parser (Commands.hs:5045): `/_connect group # [incognito=on] []` +New parser: `/_connect group # [contact= owner=] [incognito=on] []` + +`contact` and `owner` are paired — both required together. `ownerId` identifies which pre-created owner member gets the `memberContactId` set (multiple owners possible via OwnerAuth chain). + +Current type (Controller.hs:479): `APIConnectPreparedGroup GroupId IncognitoEnabled (Maybe MsgContent)` +New type: `APIConnectPreparedGroup GroupId (Maybe (ContactId, B64UrlByteString)) IncognitoEnabled (Maybe MsgContent)` + +This also benefits the UI: when tapping an owner's link in a DM, the contactId is threaded through the connect alert to `APIConnectPreparedGroup`, creating the association. + +### 4. Events.hs — new events + +`DEChatLinkReceived` — fires for ALL MCChat messages in DM (any `MsgChatLink` variant, signed or unsigned): +```haskell +| DEChatLinkReceived + { contact :: Contact, + chatItemId :: ChatItemId, + chatLink :: MsgChatLink, + ownerSig :: Maybe LinkOwnerSig + } +``` + +`DEOwnerMemberAnnounced` (from `CEvtUnknownMemberAnnounced`): +```haskell +| DEOwnerMemberAnnounced GroupInfo GroupMember GroupMember + -- ^ groupInfo, unknownMember, announcedMember +``` + +In `crDirectoryEvent_`, extend `CEvtNewChatItems` for direct chat: +```haskell +(MCChat {chatLink, ownerSig}, Nothing) -> DEChatLinkReceived ct ciId chatLink ownerSig +``` + +Add `CEvtUnknownMemberAnnounced` handler: +```haskell +CEvtUnknownMemberAnnounced {groupInfo, unknownMember, announcedMember} -> + Just $ DEOwnerMemberAnnounced groupInfo unknownMember announcedMember +``` + +### 5. Service.hs — public group link handler + +`deChatLinkReceived`: validates card, calls `apiConnectPlan` (with `resolve=on`), handles per scenario matrix. The link string comes from `MCLGroup.connLink` (`ShortLinkContact`) formatted as URI — passed via command string, parsed inside the handler. For `GLPOk` + `Verified`: joins (with contactId + ownerId), stores `dbOwnerMemberId`, registers as `GRSProposed`. On join error: replies to owner (same pattern as Service.hs:368-370). For `GLPKnown` + `Verified`: re-registration flow. + +### 6. Service.hs — owner member announced handler + +`deOwnerMemberAnnounced`: checks if the announced member's `GroupMemberId` matches `dbOwnerMemberId` of any `GRSProposed` registration. If yes and role is `GROwner`: transition to `GRSPendingApproval`, notify, send for approval. If role < `GROwner`: cancel. + +### 7. Service.hs — deGroupUpdated changes + +For public groups (`groupProfile.publicGroup` present), skip "link in welcome message" check. On profile change, call `apiConnectPlan` with `resolve=on` to get authoritative link data. Compare resolved profile against stored. If different, trigger re-approval. + +### 8. Service.hs — command restrictions and de-registration + +Check `groupProfile.publicGroup` for `/filter`, `/role`. On `/delete` for public groups, call `APILeaveGroup`. Same for owner departure/removal events. + +### 9. Help message update + +``` +To register a channel, share its link with this bot using the "Share via chat" button. +To register a group, invite this bot as admin. +``` + +### 10. Approval message for admins + +Include: group name, description, image, member count, "Registered via link sharing (signed by owner)", publicGroupId. + +### 11. Tests + +**Registration:** +- Share signed card → bot joins, owner announced, pending approval +- Share unsigned card → "must be owner" reply +- Share non-MCLGroup / non-public-group card → "only channels" reply +- Share card with invalid signature → rejection with reason +- Share card, owner never announced → stays GRSProposed +- Share card, owner announced but role < GROwner → cancelled + +**Re-registration (GLPKnown, verified):** +- Same owner re-shares, active → "already listed" +- Same owner re-shares, pending → "already pending" +- Same owner re-shares with changed profile → re-approval +- Different contact, verified owner, previous no longer owner → transfer +- Different contact, verified owner, previous still owner → "registered by another owner" +- Different contact, not owner → rejection + stale ownership check +- Same owner re-shares while GRSProposed, owner still GSMemUnknown → "waiting for owner" + +**Profile updates:** +- XGrpInfo on active public group → re-approval +- XGrpInfo on pending public group → increment approval ID +- XGrpInfo on public group skips link-in-welcome check + +**Owner tracking (via contactId association):** +- Owner role changed → suspension +- Owner leaves → removal, bot leaves +- Owner removed → removal, bot leaves + +**De-registration:** +- `/delete` by owner → removal, bot leaves +- Bot removed → removal +- Admin `/suspend` → suspension, bot stays + +**Commands:** +- `/filter` on public group → disabled +- `/role` on public group → disabled +- `/link` on public group → shows public link diff --git a/plans/2026-04-29-relay-request-retry-limit.md b/plans/2026-04-29-relay-request-retry-limit.md new file mode 100644 index 0000000000..34d0f4c9f0 --- /dev/null +++ b/plans/2026-04-29-relay-request-retry-limit.md @@ -0,0 +1,203 @@ +# Plan: Relay Request Worker Retry Limit + +## Context + +The relay request worker (`runRelayRequestWorker`) processes channel setup requests sequentially using a single worker (`relayRequestWorkerKey = 1`). When a request requires network calls to an unreachable server (e.g., fetching group link data via `getShortLinkConnReq'`), the worker retries indefinitely via `withRetryInterval` + `retryTmpError` — temp/host errors call `loop` with no limit. This blocks all subsequent relay requests from processing. + +This is an attack vector: a channel owner can create a channel link on a server unreachable by the relay, causing the relay request worker to retry forever and blocking all other channel setup requests. + +## Approach + +Follow the XFTP worker retry pattern (`runXFTPDelWorker` in `simplexmq/src/Simplex/FileTransfer/Agent.hs:667`): + +1. **Track retries and delay in DB**: Add `relay_request_retries` and `relay_request_delay` columns to the `groups` table +2. **Order by retries**: Query for next work item ordered by `relay_request_retries ASC, created_at ASC` — items with fewer retries are processed first, stuck items get pushed to the back +3. **Limit consecutive retries**: Replace `withRetryInterval` with `withRetryIntervalCount`, limiting to a small number of consecutive retries per pickup cycle (3, matching XFTP's `xftpConsecutiveRetries`). After the limit, the worker yields and picks the next item. +4. **Store delay for resumption**: On each retry, store the current backoff delay in DB. On next pickup, resume backoff from the stored delay (XFTP pattern: `ri {initialInterval = d, increaseAfter = 0}`) +5. **Expire old requests**: On temp error, before retrying, check if the request is older than 1 day and has 10+ retries — if so, mark as failed instead of retrying. Both conditions must hold — a request that's old but has few retries may just have been delayed, while a request with many retries that's recent is still being actively worked on. + +### How this neutralizes the attack + +- Attacker's request gets picked up, retried 3 times with backoff (~15s total), then yielded +- Worker picks the next item by retry count — legitimate requests (retries=0) go first +- Attacker's request accumulates retries, always processed last +- After 1 day and 10+ retries, the request is marked failed and permanently excluded + +--- + +## Detailed changes + +### 1. Database migration + +New migration: `M20260429_relay_request_retries.hs` + +```sql +ALTER TABLE groups ADD COLUMN relay_request_retries INTEGER NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN relay_request_delay INTEGER; +``` + +**Files:** +- `src/Simplex/Chat/Store/SQLite/Migrations/M20260429_relay_request_retries.hs` (new) +- `src/Simplex/Chat/Store/SQLite/Migrations.hs` (register) +- `src/Simplex/Chat/Store/Postgres/Migrations/M20260429_relay_request_retries.hs` (new) +- `src/Simplex/Chat/Store/Postgres/Migrations.hs` (register) +- `simplex-chat.cabal` (add modules) + +### 2. Extend RelayRequestData + +**File:** `src/Simplex/Chat/Types.hs` + +```haskell +data RelayRequestData = RelayRequestData + { relayInvId :: InvitationId, + reqGroupLink :: ShortLinkContact, + reqChatVRange :: VersionRangeChat, + relayRequestDelay :: Maybe Int64, + relayRequestRetries :: Int, + relayRequestCreatedAt :: UTCTime + } +``` + +- `relayRequestDelay`: resume backoff from stored position (XFTP pattern) +- `relayRequestRetries`: current retry count, used with `relayRequestCreatedAt` to decide expiry in `retryTmpError` +- `relayRequestCreatedAt`: group creation time, used for the 1-day expiry check + +### 3. Update store functions + +**File:** `src/Simplex/Chat/Store/RelayRequests.hs` + +**`getNextPendingRelayRequest`** — two changes: +- Order by `relay_request_retries ASC, created_at ASC` instead of `group_id ASC` +- SELECT and return `relay_request_delay`, `relay_request_retries`, `created_at` in the data query + +```haskell +getNextPendingRelayRequest db = + getWorkItem "relay request" getNextRequestGroupId getRelayRequestData (markRelayRequestFailed db) + where + getNextRequestGroupId = + maybeFirstRow fromOnly $ + DB.query db + [sql| + SELECT group_id FROM groups + WHERE relay_own_status = ? + AND relay_request_failed = 0 + AND relay_request_err_reason IS NULL + ORDER BY relay_request_retries ASC, created_at ASC + LIMIT 1 + |] + (Only RSInvited) + getRelayRequestData groupId = + firstRow' toRelayRequestData (SEGroupNotFound groupId) $ + DB.query db + [sql| + SELECT relay_request_inv_id, relay_request_group_link, + relay_request_peer_chat_min_version, relay_request_peer_chat_max_version, + relay_request_delay, relay_request_retries, created_at + FROM groups WHERE group_id = ? + |] + (Only groupId) + where + toRelayRequestData (Just relayInvId, Just reqGroupLink, Just minV, Just maxV, relayRequestDelay, relayRequestRetries, relayRequestCreatedAt) = + Right (groupId, RelayRequestData {relayInvId, reqGroupLink, reqChatVRange = fromMaybe (versionToRange maxV) $ safeVersionRange minV maxV, relayRequestDelay, relayRequestRetries, relayRequestCreatedAt}) + toRelayRequestData _ = Left $ SEInternalError "missing relay request data" +``` + +**New function: `updateRelayRequestRetries`**: + +```haskell +updateRelayRequestRetries :: DB.Connection -> GroupId -> Int64 -> IO () +updateRelayRequestRetries db groupId delay = do + currentTs <- getCurrentTime + DB.execute db + "UPDATE groups SET relay_request_retries = relay_request_retries + 1, relay_request_delay = ?, updated_at = ? WHERE group_id = ?" + (delay, currentTs, groupId) +``` + +Export `updateRelayRequestRetries` and `markRelayRequestFailed` from module (the latter is currently internal, used only as a callback in `getWorkItem`). + +### 4. Worker changes + +**File:** `src/Simplex/Chat/Library/Subscriber.hs` + +**Import change**: Add `withRetryIntervalCount` to the import from `Simplex.Messaging.Agent.RetryInterval`. + +**Replace `withRetryInterval` with limited retry** in `runRelayRequestOperation`: + +```haskell +runRelayRequestOperation vr user uclId = + withWork_ a doWork (withStore' getNextPendingRelayRequest) $ + \(groupId, rrd@RelayRequestData {relayRequestDelay}) -> do + ri <- asks $ reconnectInterval . agentConfig . config + let ri' = maybe ri (\d -> ri {initialInterval = d, increaseAfter = 0}) relayRequestDelay + withRetryIntervalLimit ri' $ \delay loop -> do + liftIO $ waitWhileSuspended a + liftIO $ waitForUserNetwork a + processRelayRequest groupId rrd `catchAllErrors` retryTmpError loop groupId rrd delay + where + maxConsecutiveRetries :: Int + maxConsecutiveRetries = 3 + withRetryIntervalLimit :: RetryInterval -> (Int64 -> CM () -> CM ()) -> CM () + withRetryIntervalLimit ri action = + withRetryIntervalCount ri $ \n delay loop -> + when (n < maxConsecutiveRetries) $ action delay loop + retryTmpError :: CM () -> GroupId -> RelayRequestData -> Int64 -> ChatError -> CM () + retryTmpError loop groupId RelayRequestData {relayRequestRetries, relayRequestCreatedAt} delay = \case + ChatErrorAgent {agentError} | temporaryOrHostError agentError -> do + currentTs <- liftIO getCurrentTime + if relayRequestRetries >= 10 && diffUTCTime currentTs relayRequestCreatedAt > nominalDay + then withStore' $ \db -> markRelayRequestFailed db groupId + else do + withStore' $ \db -> updateRelayRequestRetries db groupId delay + loop + e -> do + withStore' $ \db -> setRelayRequestErr db groupId (tshow e) + eToView e +``` + +Key changes from current code: +- `withRetryInterval` → `withRetryIntervalCount` wrapped in local `withRetryIntervalLimit` +- Resume from stored delay via `ri'` (XFTP pattern) +- `retryTmpError` receives the full `RelayRequestData` record and destructures the fields it needs +- On temp error: checks if request is older than 1 day with 10+ retries — if so, marks as failed instead of retrying; otherwise increments retries and calls `loop` +- After `maxConsecutiveRetries` (3), the `when` guard exits, worker picks next item + +--- + +## Files to modify + +| File | Change | +|------|--------| +| `src/Simplex/Chat/Store/SQLite/Migrations/M20260429_relay_request_retries.hs` | New migration | +| `src/Simplex/Chat/Store/SQLite/Migrations.hs` | Register migration | +| `src/Simplex/Chat/Store/Postgres/Migrations/M20260429_relay_request_retries.hs` | New migration | +| `src/Simplex/Chat/Store/Postgres/Migrations.hs` | Register migration | +| `simplex-chat.cabal` | Add migration modules | +| `src/Simplex/Chat/Types.hs` | Add `relayRequestDelay`, `relayRequestRetries`, `relayRequestCreatedAt` to `RelayRequestData` | +| `src/Simplex/Chat/Store/RelayRequests.hs` | Retry ordering, `updateRelayRequestRetries` | +| `src/Simplex/Chat/Library/Subscriber.hs` | Limited retry with delay storage, expiry check in `retryTmpError` | + +## Verification + +1. **Build**: `cabal build --ghc-options=-O0` +2. **Run relay tests**: `cabal test simplex-chat-test --test-options='-m "relay"'` +3. **Scenarios**: + - Request to unreachable server: retried 3 times per cycle, pushed to back of queue, marked failed after 1 day and 10+ retries + - Request to reachable server: succeeds on first attempt, unaffected by changes + - Multiple pending requests: stuck request doesn't block others — items with fewer retries processed first + - App restart with expired pending requests: worker starts, picks up expired request, attempts it — if it succeeds (server now reachable), completes normally; if it fails, `retryTmpError` marks it failed + +## Known considerations + +1. **Single stuck item re-pickup**: If only one request is pending and it's stuck, the worker picks it up repeatedly (3 retries each cycle, immediate re-pickup). This is acceptable — backoff grows via stored delay, and the request is marked failed after 1 day and 10+ retries. The main protection is that other requests aren't blocked. + +2. **`hasPendingRelayRequests` unchanged**: Expired requests still match the `hasPendingRelayRequests` query at startup, so the worker starts. It picks them up, attempts processing — if the server became reachable, the request succeeds normally. If it fails, `retryTmpError` checks the expiry condition and marks it failed. This is strictly better than filtering at query time: expired items get one last chance. + +3. **Delay resumption across pickups**: Stored delay resumes backoff at the last level (XFTP pattern). After many cycles, delay reaches `maxInterval` and stays there. This means retry frequency stabilizes at a low rate for stuck items. + +4. **Permanent errors unchanged**: Non-temp errors (validation, logic) still call `setRelayRequestErr` immediately, permanently excluding the item. The retry mechanism only affects `temporaryOrHostError`. + +5. **`withWork_` re-signals work**: After the action returns (hitting max consecutive retries), `withWork_` has already called `hasWork` (re-signaling the doWork TMVar). The outer `forever` loop immediately proceeds to the next iteration. This is the desired behavior — the worker processes all pending items before waiting. + +6. **`retries` count is from pickup time**: The `relayRequestRetries` value in `retryTmpError` is the count loaded when the item was picked up. Within a single pickup cycle (up to 3 consecutive retries), `updateRelayRequestRetries` increments the DB count but the local value stays the same. The expiry check uses the pickup-time count, which is at most 3 behind the DB. This is acceptable — the threshold (10) has margin. + +7. **Migration column defaults**: `relay_request_retries NOT NULL DEFAULT 0` ensures existing pending requests start with 0 retries. `relay_request_delay` is nullable (NULL = use default reconnectInterval), matching the `Maybe Int64` field. diff --git a/scripts/android/build-android-bundle.sh b/scripts/android/build-android-bundle.sh index 554e64edff..b784da2aad 100755 --- a/scripts/android/build-android-bundle.sh +++ b/scripts/android/build-android-bundle.sh @@ -23,5 +23,5 @@ unzip -o "$tmp/libsimplex.zip" -d "$tmp/simplex-chat/apps/multiplatform/common/s curl -sSf "$libsup" -o "$tmp/libsupport.zip" unzip -o "$tmp/libsupport.zip" -d "$tmp/simplex-chat/apps/multiplatform/common/src/commonMain/cpp/android/libs/arm64-v8a" -gradle -p "$tmp/simplex-chat/apps/multiplatform/" clean build +gradle -p "$tmp/simplex-chat/apps/multiplatform/" -Psimplex.assets.dir=../../assets clean build cp "$tmp/simplex-chat/apps/multiplatform/android/build/outputs/apk/release/android-release-unsigned.apk" "$PWD/simplex-chat.apk" diff --git a/scripts/android/build-android.sh b/scripts/android/build-android.sh index afd13011c9..7edee9c304 100755 --- a/scripts/android/build-android.sh +++ b/scripts/android/build-android.sh @@ -67,13 +67,13 @@ checks() { if ! command -v "$i" > /dev/null 2>&1; then commands_failed="$i $commands_failed" else - gradle_ver_local="$(gradle -v | grep Gradle | awk '{print $2}')" - gradle_ver_local_compare="$(printf ${gradle_ver_local:-0.0} | awk -F. '{print $1$2}')" + gradle_ver_local="$(gradle --version | sed -n 's/^Gradle //p')" + gradle_ver_local_compare="$(printf '%s' "$gradle_ver_local" | awk -F. '{print $1"."$2}')" gradle_ver_remote="$(grep distributionUrl ${folder}/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties)" gradle_ver_remote="${gradle_ver_remote#*-}" gradle_ver_remote="${gradle_ver_remote%-*}" - gradle_ver_remote_compare="$(printf ${gradle_ver_remote} | awk -F. '{print $1$2}')" - + gradle_ver_remote_compare="$(printf '%s' "$gradle_ver_remote" | awk -F. '{print $1"."$2}')" + if [ "$gradle_ver_local_compare" != "$gradle_ver_remote_compare" ]; then commands_failed="$i[installed=${gradle_ver_local},required=${gradle_ver_remote}] $commands_failed" fi @@ -134,7 +134,7 @@ build() { # Build only one arch sed -i.bak "s/include(.*/include(\"${android_arch}\")/" "$folder/apps/multiplatform/android/build.gradle.kts" - gradle -p "$folder/apps/multiplatform/" clean :android:assembleRelease + gradle -p "$folder/apps/multiplatform/" -Psimplex.assets.dir=../../assets clean :android:assembleRelease mkdir -p "$android_tmp_folder" unzip -oqd "$android_tmp_folder" "$android_apk_output" diff --git a/scripts/android/copy-assets.sh b/scripts/android/copy-assets.sh deleted file mode 100755 index c76383faae..0000000000 --- a/scripts/android/copy-assets.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -set -eu - -# Copies generated multiplatform assets into the SimpleX assets directory. -# Called by Gradle build when simplex.assets.dir property is set. -# -# Usage: copy-assets.sh - -SRC_DIR="$1/multiplatform/resources/MR/images" -DEST_DIR="$2/MR/images" - -if [ ! -d "$SRC_DIR" ]; then - echo "Error: source assets not found: $SRC_DIR (run resize.sh first)" >&2 - exit 1 -fi - -rm -rf "$DEST_DIR" -mkdir -p "$DEST_DIR" - -cp "$SRC_DIR"/* "$DEST_DIR/" -echo "Copied multiplatform assets to $DEST_DIR" diff --git a/scripts/ci/build-desktop-mac.sh b/scripts/ci/build-desktop-mac.sh index 9adea013b4..044d407b3a 100755 --- a/scripts/ci/build-desktop-mac.sh +++ b/scripts/ci/build-desktop-mac.sh @@ -16,5 +16,10 @@ security unlock-keychain -p "" /tmp/simplex.keychain security list-keychains -s `security list-keychains | xargs` /tmp/simplex.keychain scripts/desktop/build-lib-mac.sh cd apps/multiplatform -./gradlew packageDmg -./gradlew notarizeDmg +if [ -n "${ASSETS_DIR:-}" ]; then + set -- -Psimplex.assets.dir="$ASSETS_DIR" +else + set -- +fi +./gradlew "$@" packageDmg +./gradlew "$@" notarizeDmg diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index 7868a125b6..a2684b87d2 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -8,6 +8,7 @@ function readlink() { OS=linux ARCH="$(uname -m)" +DATABASE_BACKEND="${1:-sqlite}" GHC_VERSION=9.6.3 if [ "$ARCH" == "aarch64" ]; then @@ -25,7 +26,13 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done #rm -rf $BUILD_DIR -cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -optl-Wl,-soname,libsimplex.so -flink-rts -threaded' --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' +if [[ "$DATABASE_BACKEND" == "postgres" ]]; then + echo "Building with postgres backend..." + cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -optl-Wl,-soname,libsimplex.so -flink-rts -threaded' --constraint 'simplexmq +client_library +client_postgres' --constraint 'simplex-chat +client_library +client_postgres' +else + echo "Building with sqlite backend..." + cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -optl-Wl,-soname,libsimplex.so -flink-rts -threaded' --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' +fi cd $BUILD_DIR/build mv libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so libsimplex.so 2> /dev/null || true #patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libsimplex.so diff --git a/scripts/desktop/make-appimage-linux.sh b/scripts/desktop/make-appimage-linux.sh index c242b63d54..650c98d522 100755 --- a/scripts/desktop/make-appimage-linux.sh +++ b/scripts/desktop/make-appimage-linux.sh @@ -18,7 +18,12 @@ libcrypto_path=$(ldd common/src/commonMain/cpp/desktop/libs/*/libHSdirect-sqlcip trap "rm common/src/commonMain/cpp/desktop/libs/*/`basename $libcrypto_path` 2> /dev/null || true" EXIT cp $libcrypto_path common/src/commonMain/cpp/desktop/libs/* -./gradlew createDistributable +if [ -n "${ASSETS_DIR:-}" ]; then + set -- -Psimplex.assets.dir="$ASSETS_DIR" +else + set -- +fi +./gradlew "$@" createDistributable rm common/src/commonMain/cpp/desktop/libs/*/`basename $libcrypto_path` rm -rf $release_app_dir/AppDir 2>/dev/null diff --git a/scripts/desktop/make-deb-linux.sh b/scripts/desktop/make-deb-linux.sh index 3226c22709..0fa543a81d 100755 --- a/scripts/desktop/make-deb-linux.sh +++ b/scripts/desktop/make-deb-linux.sh @@ -4,7 +4,12 @@ ARCH="$(uname -m)" scripts/desktop/build-lib-linux.sh cd apps/multiplatform -./gradlew packageDeb +if [ -n "${ASSETS_DIR:-}" ]; then + set -- -Psimplex.assets.dir="$ASSETS_DIR" +else + set -- +fi +./gradlew "$@" packageDeb # Workaround for skiko library # diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 0d628c1c67..b8c329431c 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,28 @@ + + https://simplex.chat/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html + +

New in v6.5.

+

Public channels - speak freely!

+
    +
  • Reliability: many relays per channel.
  • +
  • Ownership: you can run your own relays.
  • +
  • Security: owners hold channel keys.
  • +
  • Privacy: for owners and subscribers.
  • +
+

Easier to invite your friends: we made connecting simpler for new users.

+

Safe web links:

+
    +
  • opt-in to send link previews.
  • +
  • use SOCKS proxy for previews (if enabled).
  • +
  • prevent hyperlink phishing.
  • +
  • remove link tracking.
  • +
+

Non-profit governance: to make SimpleX Network last.

+
+
https://simplex.chat/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.html diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 9fe726a02b..13aa440e7b 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."858fac7f4f821a2df6fbea03a1bfbb82ea9717c5" = "1fhzynf80db7h6y2wv61fsdfd80f0blja9ljsfh405r11yg2yxvi"; + "https://github.com/simplex-chat/simplexmq.git"."1f173abf6d6fccb617be1e7994629c405983c431" = "1myfs7yi8bmbrzapbhz6rmvxknpdzv6rxyypg811mhsw7rfphn65"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/scripts/simplex-chat-reproduce-builds.sh b/scripts/simplex-chat-reproduce-builds.sh index b05d41efbc..d512735bf1 100755 --- a/scripts/simplex-chat-reproduce-builds.sh +++ b/scripts/simplex-chat-reproduce-builds.sh @@ -110,7 +110,7 @@ for os_pair in ${oses}; do # Desktop: deb docker exec \ -t "${container_name}" \ - sh -c './scripts/desktop/make-deb-linux.sh' + sh -c "export ASSETS_DIR='../../assets'; ./scripts/desktop/make-deb-linux.sh" # Copy deb docker cp \ @@ -128,7 +128,7 @@ for os_pair in ${oses}; do # Appimage docker exec \ -t "${container_name}" \ - sh -c './scripts/desktop/make-appimage-linux.sh && mv ./apps/multiplatform/release/main/*imple*.AppImage ./apps/multiplatform/release/main/simplex.appimage' + sh -c "export ASSETS_DIR='../../assets'; ./scripts/desktop/make-appimage-linux.sh && mv ./apps/multiplatform/release/main/*imple*.AppImage ./apps/multiplatform/release/main/simplex.appimage" # Copy appimage docker cp \ diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 320e51c8af..3da5cf1422 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.5.0.15 +version: 6.5.1.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat @@ -130,6 +130,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20260122_has_link Simplex.Chat.Store.Postgres.Migrations.M20260222_chat_relays Simplex.Chat.Store.Postgres.Migrations.M20260403_item_viewed + Simplex.Chat.Store.Postgres.Migrations.M20260429_relay_request_retries else exposed-modules: Simplex.Chat.Archive @@ -282,6 +283,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20260122_has_link Simplex.Chat.Store.SQLite.Migrations.M20260222_chat_relays Simplex.Chat.Store.SQLite.Migrations.M20260403_item_viewed + Simplex.Chat.Store.SQLite.Migrations.M20260429_relay_request_retries other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 2671774603..c5f17e5d69 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -10,6 +10,7 @@ {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE TupleSections #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} @@ -26,7 +27,7 @@ import qualified Data.List.NonEmpty as L import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, mapMaybe) import Data.Text (Text) -import Data.Time.Clock (getCurrentTime) +import Data.Time.Clock (getCurrentTime, nominalDay) import Simplex.Chat.Controller import Simplex.Chat.Library.Commands import Simplex.Chat.Operators @@ -42,6 +43,7 @@ import Simplex.Chat.Util (shuffle) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Agent import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg) +import Simplex.Messaging.Agent.RetryInterval (RetryInterval (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.Common (DBStore (dbNew)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -115,6 +117,8 @@ defaultChatConfig = deliveryWorkerDelay = 0, deliveryBucketSize = 10000, channelSubscriberRole = GRObserver, + relayRequestRetryInterval = RetryInterval {initialInterval = 5_000000, increaseAfter = 0, maxInterval = 600_000000}, + relayRequestExpiry = (10, nominalDay), deviceNameForRemote = "", remoteCompression = True, chatHooks = defaultChatHooks diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index e989e520a5..cfb60c360a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -73,6 +73,7 @@ import Simplex.Messaging.Agent (AgentClient, DatabaseDiff, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg, Worker) import Simplex.Messaging.Agent.Lock +import Simplex.Messaging.Agent.RetryInterval (RetryInterval (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction, withTransactionPriority) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, UpMigration) @@ -158,6 +159,8 @@ data ChatConfig = ChatConfig deliveryWorkerDelay :: Int64, -- microseconds deliveryBucketSize :: Int, channelSubscriberRole :: GroupMemberRole, -- TODO [relays] starting role should be communicated in protocol from owner to relays + relayRequestRetryInterval :: RetryInterval, + relayRequestExpiry :: (Int, NominalDiffTime), highlyAvailable :: Bool, deviceNameForRemote :: Text, remoteCompression :: Bool, @@ -470,13 +473,13 @@ data ChatCommand | AddContact IncognitoEnabled | APISetConnectionIncognito Int64 IncognitoEnabled | APIChangeConnectionUser Int64 UserId -- new user id to switch connection to - | APIConnectPlan {userId :: UserId, connectionLink :: Maybe AConnectionLink, linkOwnerSig :: Maybe LinkOwnerSig} -- Maybe AConnectionLink is used to report link parsing failure as special error + | APIConnectPlan {userId :: UserId, connectionLink :: Maybe AConnectionLink, resolveKnown :: Bool, linkOwnerSig :: Maybe LinkOwnerSig} -- Maybe AConnectionLink is used to report link parsing failure as special error | APIPrepareContact UserId ACreatedConnLink ContactShortLinkData | APIPrepareGroup UserId CreatedLinkContact DirectLink GroupShortLinkData | APIChangePreparedContactUser ContactId UserId | APIChangePreparedGroupUser GroupId UserId | APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent} - | APIConnectPreparedGroup GroupId IncognitoEnabled (Maybe MsgContent) + | APIConnectPreparedGroup {groupId :: GroupId, incognito :: IncognitoEnabled, ownerContact :: Maybe GroupOwnerContact, msgContent_ :: Maybe MsgContent} | APIConnect {userId :: UserId, incognito :: IncognitoEnabled, preparedLink_ :: Maybe ACreatedConnLink} -- Maybe is used to report link parsing failure as special error | Connect {incognito :: IncognitoEnabled, connLink_ :: Maybe AConnectionLink} | APIConnectContactViaAddress UserId IncognitoEnabled ContactId @@ -1037,15 +1040,27 @@ data GroupLinkPlan | GLPOwnLink {groupInfo :: GroupInfo} | GLPConnectingConfirmReconnect | GLPConnectingProhibit {groupInfo_ :: Maybe GroupInfo} - | GLPKnown {groupInfo :: GroupInfo} + | GLPKnown {groupInfo :: GroupInfo, groupUpdated :: BoolDef, ownerVerification :: Maybe OwnerVerification, linkOwners :: ListDef GroupLinkOwner} | GLPNoRelays {groupSLinkData_ :: Maybe GroupShortLinkData} deriving (Show) +data GroupLinkOwner = GroupLinkOwner + { memberId :: MemberId, + memberKey :: C.PublicKeyEd25519 + } + deriving (Show) + data OwnerVerification = OVVerified | OVFailed {reason :: Text} deriving (Show) +data GroupOwnerContact = GroupOwnerContact + { contactId :: ContactId, + memberId :: MemberId + } + deriving (Show) + type DirectLink = Bool data GroupShortLinkInfo = GroupShortLinkInfo @@ -1656,6 +1671,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CAP") ''ContactAddressPlan) $(JQ.deriveJSON defaultJSON ''GroupShortLinkInfo) +$(JQ.deriveJSON defaultJSON ''GroupLinkOwner) + $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 042280e062..31e6533ad3 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -625,7 +625,10 @@ processChatCommand vr nm = \case mapM_ assertNoMentions cms withContactLock "sendMessage" chatId $ sendContactContentMessages user chatId live itemTTL (L.map composedMessageReq cms) - SRGroup chatId gsScope asGroup -> + SRGroup chatId gsScope asGroup -> do + case gsScope of + Just (GCSMemberSupport _) -> when asGroup $ throwCmdError "cannot send as group in support scope" + Nothing -> pure () withGroupLock "sendMessage" chatId $ do (gInfo, cmrs) <- withFastStore $ \db -> do g <- getGroupInfo db vr user chatId @@ -1772,15 +1775,14 @@ processChatCommand vr nm = \case APIGroupInfo gId -> withUser $ \user -> CRGroupInfo user <$> withFastStore (\db -> getGroupInfo db vr user gId) APIGetUpdatedGroupLinkData groupId -> withUser $ \user -> do - gInfo@GroupInfo {groupProfile = GroupProfile {publicGroup}} <- withFastStore $ \db -> getGroupInfo db vr user groupId - case publicGroup of - Just PublicGroupProfile {groupLink = sLnk} | useRelays' gInfo -> do + gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} <- withFastStore $ \db -> getGroupInfo db vr user groupId + case p of + GroupProfile {publicGroup = Just PublicGroupProfile {groupLink = sLnk}} | useRelays' gInfo -> do (_, cData) <- getShortLinkConnReq nm user sLnk groupSLinkData_ <- liftIO $ decodeLinkUserData cData - let publicGroupData_ = groupSLinkData_ >>= \GroupShortLinkData {publicGroupData} -> publicGroupData - publicMemberCount_ = (\PublicGroupData {publicMemberCount} -> publicMemberCount) <$> publicGroupData_ - gInfo' <- fromMaybe gInfo - <$> forM publicMemberCount_ (\count -> withFastStore $ \db -> setPublicMemberCount db vr user gInfo count) + gInfo' <- case groupSLinkData_ of + Just sLinkData -> fst <$> updateGroupFromLinkData user gInfo sLinkData + _ -> pure gInfo pure $ CRGroupInfo user gInfo' _ -> throwCmdError "group link data not available" APIGroupMemberInfo gId gMemberId -> withUser $ \user -> do @@ -1978,9 +1980,9 @@ processChatCommand vr nm = \case createDirectConnection db newUser agConnId ccLink' Nothing ConnNew Nothing subMode initialChatVersion PQSupportOn deleteAgentConnectionAsync (aConnId' conn) pure conn' - APIConnectPlan userId (Just cLink) linkOwnerSig_ -> withUserId userId $ \user -> - uncurry (CRConnectionPlan user) <$> connectPlan user cLink linkOwnerSig_ - APIConnectPlan _ Nothing _ -> throwChatError CEInvalidConnReq + APIConnectPlan userId (Just cLink) resolveKnown linkOwnerSig_ -> withUserId userId $ \user -> + uncurry (CRConnectionPlan user) <$> connectPlan user cLink resolveKnown linkOwnerSig_ + APIConnectPlan _ Nothing _ _ -> throwChatError CEInvalidConnReq APIPrepareContact userId accLink contactSLinkData -> withUserId userId $ \user -> do let ContactShortLinkData {profile, message, business} = contactSLinkData welcomeSharedMsgId <- forM message $ \_ -> getSharedMsgId @@ -2009,7 +2011,7 @@ processChatCommand vr nm = \case let cd = CDDirectRcv ct createItem sharedMsgId content = createChatItem user cd False content sharedMsgId Nothing cInfo = DirectChat ct - void $ createItem Nothing $ CIRcvDirectE2EEInfo $ E2EInfo $ connRequestPQEncryption cReq + void $ createItem Nothing $ CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ connRequestPQEncryption cReq void $ createFeatureEnabledItems_ user ct aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent) message let chat = case aci of @@ -2100,7 +2102,7 @@ processChatCommand vr nm = \case toView $ CEvtNewChatItems user [ci] pure $ CRStartedConnectionToContact user ct' customUserProfile CVRConnectedContact ct' -> pure $ CRContactAlreadyExists user ct' - APIConnectPreparedGroup groupId incognito msgContent_ -> withUser $ \user -> do + APIConnectPreparedGroup {groupId, incognito, ownerContact, msgContent_} -> withUser $ \user -> do gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId case gInfo of GroupInfo {preparedGroup = Nothing} -> throwCmdError "group doesn't have link to connect" @@ -2126,8 +2128,12 @@ processChatCommand vr nm = \case gInfo' <- withFastStore $ \db -> do gInfo' <- updatePreparedRelayedGroup db vr user gInfo mainCReq cReqHash incognitoProfile rootKey memberPrivKey publicMemberCount_ -- Pre-emptively create owner members with trusted keys from link data - forM_ owners $ \OwnerAuth {ownerId, ownerKey} -> - void $ createLinkOwnerMember db vr user gInfo' (MemberId ownerId) ownerKey + forM_ owners $ \OwnerAuth {ownerId, ownerKey} -> do + let ctId_ = case ownerContact of + Just GroupOwnerContact {contactId, memberId} + | memberId == MemberId ownerId -> Just contactId + _ -> Nothing + void $ createLinkOwnerMember db vr user gInfo' ctId_ (MemberId ownerId) ownerKey pure gInfo' rs <- mapConcurrently (connectToRelay gInfo') relays let relayFailed = \case (_, _, Left _) -> True; _ -> False @@ -2221,7 +2227,7 @@ processChatCommand vr nm = \case Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do -- TODO [relays] member: /c api to support groups with relays -- TODO - possibly by going through APIPrepareGroup -> APIConnectPreparedGroup - (ccLink, plan) <- connectPlan user cLink Nothing `catchAllErrors` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing Nothing)); _ -> throwError e + (ccLink, plan) <- connectPlan user cLink False Nothing `catchAllErrors` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing Nothing)); _ -> throwError e connectWithPlan user incognito ccLink plan Connect _ Nothing -> throwChatError CEInvalidConnReq APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do @@ -2372,7 +2378,7 @@ processChatCommand vr nm = \case forM scope_ $ \(GSNMemberSupport mName_) -> GCSMemberSupport <$> mapM (getGroupMemberIdByName db user gId) mName_ (gInfo, cScope_,) <$> liftIO (getMessageMentions db user gId msg) - let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo) + let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo cScope_) processChatCommand vr nm $ APISendMessages sendRef False Nothing [ComposedMessage Nothing Nothing mc mentions] SNLocal -> do folderId <- withFastStore (`getUserNoteFolderId` user) @@ -3125,7 +3131,7 @@ processChatCommand vr nm = \case qiId <- getGroupChatItemIdByText db user gId cName quotedMsg (gInfo, qiId,) <$> liftIO (getMessageMentions db user gId msg) let mc = MCText msg - processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions] + processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo Nothing)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions] ClearNoteFolder -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) processChatCommand vr nm $ APIClearChat (ChatRef CTLocal folderId Nothing) @@ -3401,7 +3407,7 @@ processChatCommand vr nm = \case _ -> throwCmdError "not supported" pure $ ChatRef cType chatId Nothing getSendAsGroup :: User -> ChatRef -> CM ShowGroupAsSender - getSendAsGroup user' (ChatRef CTGroup chatId _) = sendAsGroup' <$> withFastStore (\db -> getGroupInfo db vr user' chatId) + getSendAsGroup user' (ChatRef CTGroup chatId scope) = (`sendAsGroup'` scope) <$> withFastStore (\db -> getGroupInfo db vr user' chatId) getSendAsGroup _ _ = pure False getChatRefAndMentions :: User -> ChatName -> Text -> CM (ChatRef, Map MemberName GroupMemberId) getChatRefAndMentions user cName msg = do @@ -3855,7 +3861,7 @@ processChatCommand vr nm = \case createNewGroupItems user gInfo = do let cd = CDGroupSnd gInfo Nothing createInternalChatItem user cd CIChatBanner (Just epochStart) - createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing createGroupFeatureItems user cd CISndGroupFeature gInfo sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM () sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership, businessChat} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do @@ -3978,8 +3984,8 @@ processChatCommand vr nm = \case pure (gId, chatSettings) _ -> throwCmdError "not supported" processChatCommand vr nm $ APISetChatSettings (ChatRef cType chatId Nothing) $ updateSettings chatSettings - connectPlan :: User -> AConnectionLink -> Maybe LinkOwnerSig -> CM (ACreatedConnLink, ConnectionPlan) - connectPlan user (ACL SCMInvitation cLink) sig_ = case cLink of + connectPlan :: User -> AConnectionLink -> Bool -> Maybe LinkOwnerSig -> CM (ACreatedConnLink, ConnectionPlan) + connectPlan user (ACL SCMInvitation cLink) _ sig_ = case cLink of CLFull cReq -> invitationReqAndPlan cReq Nothing Nothing Nothing CLShort l -> do let l' = serverShortLink l @@ -4000,7 +4006,7 @@ processChatCommand vr nm = \case invitationReqAndPlan cReq sLnk_ cld ov = do plan <- invitationRequestPlan user cReq cld ov `catchAllErrors` (pure . CPError) pure (ACCL SCMInvitation (CCLink cReq sLnk_), plan) - connectPlan user (ACL SCMContact cLink) sig_ = case cLink of + connectPlan user (ACL SCMContact cLink) resolveKnown sig_ = case cLink of CLFull cReq -> do plan <- contactOrGroupRequestPlan user cReq `catchAllErrors` (pure . CPError) pure (ACCL SCMContact $ CCLink cReq Nothing, plan) @@ -4033,9 +4039,11 @@ processChatCommand vr nm = \case where l' = serverShortLink l con cReq = ACCL SCMContact $ CCLink cReq (Just l') - gPlan (cReq, g) = if memberRemoved (membership g) then Nothing else Just (con cReq, CPGroupLink (GLPKnown g)) + gPlan (cReq, g) = if memberRemoved (membership g) then Nothing else Just (con cReq, CPGroupLink (GLPKnown g (BoolDef False) Nothing (ListDef []))) groupShortLinkPlan = knownLinkPlans >>= \case + Just (_, CPGroupLink (GLPKnown g _ _ _)) + | resolveKnown -> resolveKnownGroup g Just r -> pure r Nothing -> do (fd, cData@(ContactLinkData _ UserContactData {direct, owners, relays})) <- getShortLinkConnReq' nm user l' @@ -4045,8 +4053,6 @@ processChatCommand vr nm = \case else do let FixedLinkData {linkConnReq = cReq, linkEntityId, rootKey} = fd linkInfo = GroupShortLinkInfo {direct, groupRelays = relays, publicGroupId = B64UrlByteString <$> linkEntityId} - -- Cross-validate linkEntityId and publicGroupId from profile: - -- for channels both must be present and match, for p2p groups both must be absent let profilePGId = groupSLinkData_ >>= \GroupShortLinkData {groupProfile = GroupProfile {publicGroup}} -> fmap (\PublicGroupProfile {publicGroupId} -> publicGroupId) publicGroup case (B64UrlByteString <$> linkEntityId, profilePGId) of @@ -4061,6 +4067,15 @@ processChatCommand vr nm = \case liftIO (getGroupInfoViaUserShortLink db vr user l') >>= \case Just (cReq, g) -> pure $ Just (con cReq, CPGroupLink (GLPOwnLink g)) Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db vr user l' + resolveKnownGroup g = do + (fd@FixedLinkData {rootKey = rk}, cData@(ContactLinkData _ UserContactData {owners})) <- getShortLinkConnReq' nm user l' + groupSLinkData_ <- liftIO $ decodeLinkUserData cData + let ov = verifyLinkOwner rk owners l' sig_ + glOwners = map (\OwnerAuth {ownerId, ownerKey} -> GroupLinkOwner {memberId = MemberId ownerId, memberKey = ownerKey}) owners + (g', updated) <- case groupSLinkData_ of + Just sLinkData -> updateGroupFromLinkData user g sLinkData + _ -> pure (g, False) + pure (con (linkConnReq fd), CPGroupLink (GLPKnown g' (BoolDef updated) ov (ListDef glOwners))) connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse connectWithPlan user@User {userId} incognito ccLink plan | connectionPlanProceed plan = do @@ -4140,10 +4155,10 @@ processChatCommand vr nm = \case (Just gInfo, _) -> groupPlan gInfo linkInfo gld ov groupPlan :: GroupInfo -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan groupPlan gInfo@GroupInfo {membership} linkInfo gld ov - | memberStatus membership == GSMemRejected = pure $ CPGroupLink (GLPKnown gInfo) + | memberStatus membership == GSMemRejected = pure $ CPGroupLink (GLPKnown gInfo (BoolDef False) ov (ListDef [])) | not (memberActive membership) && not (memberRemoved membership) = pure $ CPGroupLink (GLPConnectingProhibit $ Just gInfo) - | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo) + | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo (BoolDef False) ov (ListDef [])) | otherwise = pure $ CPGroupLink (GLPOk linkInfo gld ov) contactCReqSchemas :: ConnReqUriData -> (ConnReqContact, ConnReqContact) contactCReqSchemas crData = @@ -4478,7 +4493,7 @@ processChatCommand vr nm = \case ChatRef CTDirect cId _ -> a $ SRDirect cId ChatRef CTGroup gId scope -> do gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId - a $ SRGroup gId scope (sendAsGroup' gInfo) + a $ SRGroup gId scope (sendAsGroup' gInfo scope) _ -> throwCmdError "not supported" getSharedMsgId :: CM SharedMsgId getSharedMsgId = do @@ -5008,7 +5023,7 @@ chatCommandP = ("/help" <|> "/h") $> ChatHelp HSMain, ("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile), "/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP), - ("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> groupProfile), + ("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> channelProfile), "/_public group " *> (APINewPublicGroup <$> A.decimal <*> incognitoOnOffP <*> _strP <* A.space <*> jsonP), "/_get relays #" *> (APIGetGroupRelays <$> A.decimal), ("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)), @@ -5051,13 +5066,13 @@ chatCommandP = (">#" <|> "> #") *> (SendGroupMessageQuote <$> displayNameP <* A.space <* char_ '@' <*> (Just <$> displayNameP) <* A.space <*> quotedMsg <*> msgTextP), "/_contacts " *> (APIListContacts <$> A.decimal), "/contacts" $> ListContacts, - "/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> ((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing) <*> optional (" sig=" *> jsonP)), + "/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> ((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing) <*> ((" resolve=" *> onOffP) <|> pure False) <*> optional (" sig=" *> jsonP)), "/_prepare contact " *> (APIPrepareContact <$> A.decimal <* A.space <*> connLinkP <* A.space <*> jsonP), "/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP' <*> (" direct=" *> onOffP <|> pure True) <* A.space <*> jsonP), "/_set contact user @" *> (APIChangePreparedContactUser <$> A.decimal <* A.space <*> A.decimal), "/_set group user #" *> (APIChangePreparedGroupUser <$> A.decimal <* A.space <*> A.decimal), "/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), - "/_connect group #" *> (APIConnectPreparedGroup <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), + "/_connect group #" *> (APIConnectPreparedGroup <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> ownerContactP) <*> optional (A.space *> msgContentP)), "/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP), "/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> connLinkP_), "/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP), @@ -5138,6 +5153,7 @@ chatCommandP = "/set disappear @" *> (SetContactTimedMessages <$> displayNameP <*> optional (A.space *> timedMessagesEnabledP)), "/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))), "/set reports #" *> (SetGroupFeature (AGFNR SGFReports) <$> displayNameP <*> _strP), + "/set support #" *> (SetGroupFeature (AGFNR SGFSupport) <$> displayNameP <*> (A.space *> strP)), "/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole), "/set admission review #" *> (SetGroupMemberAdmissionReview <$> displayNameP <*> (A.space *> memberCriteriaP)), ("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito, @@ -5187,6 +5203,7 @@ chatCommandP = ((Just <$> connLinkP) <|> A.takeTill (== ' ') $> Nothing) incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False + ownerContactP = "contact=" *> (GroupOwnerContact <$> A.decimal <* " owner=" <*> strP) imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,") imageP = safeDecodeUtf8 <$> ((<>) <$> imagePrefix <*> (B64.encode <$> base64P)) chatTypeP = A.char '@' $> CTDirect <|> A.char '#' $> CTGroup <|> A.char '*' $> CTLocal <|> A.char ':' $> CTContactConnection @@ -5274,6 +5291,10 @@ chatCommandP = history = Just HistoryGroupPreference {enable = FEOn} } pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, publicGroup = Nothing, groupPreferences, memberAdmission = Nothing} + channelProfile = do + p@GroupProfile {groupPreferences = prefs_} <- groupProfile + let prefs = (fromMaybe emptyGroupPrefs prefs_) {support = Just SupportGroupPreference {enable = FEOff}} :: GroupPreferences + pure p {groupPreferences = Just prefs} memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing) shortDescrP = do descr <- A.takeWhile1 isSpace *> (T.dropWhileEnd isSpace <$> textP) <|> pure "" diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index f82b6884b2..d7de3a52ad 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -338,12 +338,17 @@ quoteContent mc qmc ciFile_ prohibitedGroupContent :: GroupInfo -> GroupMember -> Maybe GroupChatScopeInfo -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature prohibitedGroupContent gInfo@GroupInfo {membership = mem@GroupMember {memberRole = userRole}} m scopeInfo mc ft file_ sent + | not supportAllowed = Just GFSupport | isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) && not hostApprovalVoice = Just GFVoice | isNothing scopeInfo && not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles | isNothing scopeInfo && isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports | isNothing scopeInfo && prohibitedSimplexLinks gInfo m mc ft = Just GFSimplexLinks | otherwise = Nothing where + supportAllowed = case scopeInfo of + Just (GCSIMemberSupport scopeMem_) -> + groupFeatureAllowed SGFSupport gInfo || isJust (supportChat $ fromMaybe mem scopeMem_) + Nothing -> True hostApprovalVoice | sent = userRole >= GRAdmin && sendApprovalPhase | otherwise = memberCategory m == GCHostMember && hostApprovalPhase @@ -1029,7 +1034,7 @@ acceptBusinessJoinRequestAsync createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode let cd = CDGroupSnd gInfo Nothing -- TODO [short links] move to profileContactRequest? - createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing createGroupFeatureItems user cd CISndGroupFeature gInfo -- TODO [short links] get updated business chat group and member? (currently not used) pure (gInfo, clientMember) @@ -1328,6 +1333,24 @@ updatePublicGroupData user gInfo pure gInfo' | otherwise = pure gInfo +updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> CM (GroupInfo, Bool) +updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData} + | profileChanged || countChanged = do + vr <- chatVersionRange + withStore $ \db -> do + g <- if profileChanged then updateGroupProfile db user gInfo groupProfile else pure gInfo + g' <- case publicGroupData of + Just PublicGroupData {publicMemberCount} | countChanged -> + setPublicMemberCount db vr user g publicMemberCount + _ -> pure g + pure (g', profileChanged) + | otherwise = pure (gInfo, False) + where + profileChanged = p /= groupProfile + countChanged = case publicGroupData of + Just PublicGroupData {publicMemberCount} -> Just publicMemberCount /= localCount + _ -> False + -- TODO [relays] owner: set owners on updating link data (multi-owner) groupLinkData :: GroupInfo -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData) groupLinkData gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}, membership = GroupMember {memberId}, groupKeys} GroupLink {groupLinkId} groupRelays = @@ -1467,7 +1490,7 @@ createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (C createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = flip catchAllErrors (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of (Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled') - (Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo $ Just pqSndEnabled') + (Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (e2eInfoEncrypted $ Just pqSndEnabled') _ -> pure (ct, conn) where createPQItem ciContent = do @@ -1482,7 +1505,7 @@ updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Conta updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' = flip catchAllErrors (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of (Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled') - (Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo $ Just pqRcvEnabled') + (Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (e2eInfoEncrypted $ Just pqRcvEnabled') _ -> pure (ct, conn) where updatePQ ciContent = do diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index e92622b60c..48dd63f6cf 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -37,7 +37,7 @@ import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, mapMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1) -import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime) +import Data.Time.Clock (NominalDiffTime, UTCTime, addUTCTime, diffUTCTime, getCurrentTime) import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 import Data.Word (Word32) @@ -77,7 +77,7 @@ import Simplex.Messaging.Agent.Client (getAgentWorker, temporaryOrHostError, wai import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), Worker (..)) import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) -import Simplex.Messaging.Agent.RetryInterval (withRetryInterval) +import Simplex.Messaging.Agent.RetryInterval (RetryInterval (..), nextRetryDelay) import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Client (NetworkRequestMode (..), ProxyClientError (..)) import qualified Simplex.Messaging.Crypto as C @@ -94,8 +94,9 @@ import Simplex.Messaging.Transport (TransportError (..)) import Simplex.Messaging.Util import Simplex.Messaging.Version import qualified System.FilePath as FP +import System.Mem.Weak (Weak) import Text.Read (readMaybe) -import UnliftIO.Concurrent (forkIO) +import UnliftIO.Concurrent (ThreadId, forkIO, mkWeakThreadId) import UnliftIO.Directory import UnliftIO.STM @@ -590,7 +591,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [incognito] print incognito profile used for this contact incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile) - let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo $ Just pqEnc) Nothing + let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ Just pqEnc) Nothing -- TODO [short links] get contact request by contactRequestId, check encryption (UserContactRequest.pqSupport)? when (directOrUsed ct') $ case (preparedContact ct', contactRequestId' ct') of (Nothing, Nothing) -> do @@ -842,7 +843,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = firstConnectedHost ( do let cd = CDGroupRcv gInfo'' scopeInfo m'' - createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CIRcvGroupE2EEInfo $ e2eInfoGroup gInfo'') Nothing let prepared = preparedGroup gInfo'' unless (isJust prepared) $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo'' memberConnectedChatItem gInfo'' scopeInfo m'' @@ -1356,7 +1357,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = upsertDirectRequestItem cd (requestMsg_, prevSharedMsgId_) Nothing -> do void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing (Just epochStart) - let e2eContent = CIRcvDirectE2EEInfo $ E2EInfo $ Just $ CR.pqSupportToEnc $ reqPQSup + let e2eContent = CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ Just $ CR.pqSupportToEnc $ reqPQSup void $ createChatItem user cd False e2eContent Nothing Nothing void $ createFeatureEnabledItems_ user ct forM_ (autoReply addressSettings) $ \mc -> forM_ welcomeSharedMsgId $ \sharedMsgId -> @@ -1492,7 +1493,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason xGrpRelayInv :: InvitationId -> VersionRangeChat -> GroupRelayInvitation -> CM () xGrpRelayInv invId chatVRange groupRelayInv = do - (_gInfo, _ownerMember) <- withStore $ \db -> createRelayRequestGroup db vr user groupRelayInv invId chatVRange + initialDelay <- asks $ initialInterval . relayRequestRetryInterval . config + (_gInfo, _ownerMember) <- withStore $ \db -> createRelayRequestGroup db vr user groupRelayInv invId chatVRange initialDelay lift $ void $ getRelayRequestWorker True xGrpRelayTest :: InvitationId -> VersionRangeChat -> ByteString -> CM () xGrpRelayTest invId chatVRange challenge = do @@ -1535,7 +1537,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = memberCanSend :: Maybe GroupMember -> Maybe MsgScope -> CM (Maybe DeliveryTaskContext) -> CM (Maybe DeliveryTaskContext) memberCanSend Nothing _ a = a -- channel message - was previously checked and allowed by relay memberCanSend (Just m@GroupMember {memberRole}) msgScope a = case msgScope of - Just MSMember {} -> a + Just (MSMember mId) + | sameMemberId mId m || memberRole >= GRModerator -> a + | otherwise -> messageError "member is not allowed to send to this support chat" $> Nothing Nothing | memberRole > GRObserver || memberPending m -> a | otherwise -> messageError "member is not allowed to send messages" $> Nothing @@ -1837,13 +1841,19 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- This patches initial sharedMsgId into chat item when locally deleted chat item -- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete). -- Chat item and update message which created it will have different sharedMsgId in this case... - let timed_ = rcvContactCITimed ct ttl - ts = ciContentTexts content - (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty - ci' <- withStore' $ \db -> do - createChatItemVersion db (chatItemId' ci) brokerTs mc - updateDirectChatItem' db user contactId ci content True live Nothing Nothing - toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci') + if isVoice mc && not (featureAllowed SCFVoice forContact ct) + then do + let ciContent = ciContentNoParse $ CIRcvChatFeatureRejected CFVoice + (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs ciContent Nothing Nothing False M.empty + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci) + else do + let timed_ = rcvContactCITimed ct ttl + ts = ciContentTexts content + (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty + ci' <- withStore' $ \db -> do + createChatItemVersion db (chatItemId' ci) brokerTs mc + updateDirectChatItem' db user contactId ci content True live Nothing Nothing + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci') where brokerTs = metaBrokerTs msgMeta content = CIRcvMsgContent mc @@ -2073,15 +2083,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = (gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m mc msgScope_ pure (gInfo', CDGroupRcv gInfo' scopeInfo m', mentions', scopeInfo) Nothing -> pure (gInfo, CDChannelRcv gInfo Nothing, mentions, Nothing) - (ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions' - ci' <- withStore' $ \db -> do - createChatItemVersion db (chatItemId' ci) brokerTs mc - updateGroupChatItem db user groupId ci content True live Nothing - ci'' <- case chatDir of - CDGroupRcv gi' _ m' -> blockedMemberCI gi' m' ci' - CDChannelRcv {} -> pure ci' - toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci'') - pure $ Just $ infoToDeliveryContext gInfo' scopeInfo showGroupAsSender + case m_ >>= \m -> prohibitedGroupContent gInfo' m scopeInfo mc ft_ (Nothing :: Maybe String) False of + Just f -> do + let ciContent = ciContentNoParse $ CIRcvGroupFeatureRejected f + (ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs ciContent Nothing timed_ False M.empty + groupMsgToView cInfo ci + pure Nothing + Nothing -> do + (ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions' + ci' <- withStore' $ \db -> do + createChatItemVersion db (chatItemId' ci) brokerTs mc + updateGroupChatItem db user groupId ci content True live Nothing + ci'' <- case chatDir of + CDGroupRcv gi' _ m' -> blockedMemberCI gi' m' ci' + CDChannelRcv {} -> pure ci' + toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci'') + pure $ Just $ infoToDeliveryContext gInfo' scopeInfo showGroupAsSender where content = CIRcvMsgContent mc ts@(_, ft_) = msgContentTexts mc @@ -2546,7 +2563,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- create item in both scopes let gInfo' = gInfo {membership = membership'} cd = CDGroupRcv gInfo' Nothing m - createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CIRcvGroupE2EEInfo $ e2eInfoGroup gInfo') Nothing let prepared = preparedGroup gInfo' unless (isJust prepared) $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo' let welcomeMsgId_ = (\PreparedGroup {welcomeSharedMsgId = mId} -> mId) <$> preparedGroup gInfo' @@ -2616,10 +2633,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = BCCustomer -> customerId == memberId createProfileUpdatedItem m' (msg, brokerTs) = do (gInfo', m'', scopeInfo) <- mkGroupChatScope gInfo m' - let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' - cd = CDGroupRcv gInfo' scopeInfo m'' - (ci, cInfo) <- saveRcvChatItemNoParse user cd msg brokerTs ciContent - groupMsgToView cInfo ci + let createItem scopeInfo_ m_ = do + let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' + cd = CDGroupRcv gInfo' scopeInfo_ m_ + (ci, cInfo) <- saveRcvChatItemNoParse user cd msg brokerTs ciContent + groupMsgToView cInfo ci + case scopeInfo of + Just _ -> createItem scopeInfo m'' + Nothing + | useRelays' gInfo' && not (isRelay m'') && memberRole' m'' < GRModerator -> + forM_ (supportChat m'') $ \_ -> + createItem (Just GCSIMemberSupport {groupMember_ = Just m''}) m'' + | otherwise -> createItem Nothing m'' xInfoProbe :: ContactOrMember -> Probe -> CM () xInfoProbe cgm2 probe = do @@ -2917,8 +2942,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = GCHostMember -> withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right existingMember - | useRelays' gInfo -> - void $ withStore $ \db -> updatePreparedChannelMember db vr user existingMember memInfo + | useRelays' gInfo -> do + updatedMember <- withStore $ \db -> updatePreparedChannelMember db vr user existingMember memInfo + toView $ CEvtGroupMemberUpdated user gInfo existingMember updatedMember | otherwise -> messageError "x.grp.mem.intro ignored: member already exists" Left _ @@ -3686,23 +3712,55 @@ runRelayRequestWorker a Worker {doWork} = do user <- getRelayUser db UserContactLink {userContactLinkId} <- getUserAddress db user pure (user, userContactLinkId) + delayThreads <- liftIO TM.emptyIO forever $ do lift $ waitForWork doWork - runRelayRequestOperation vr user uclId + runRelayRequestOperation delayThreads vr user uclId where - runRelayRequestOperation :: VersionRangeChat -> User -> Int64 -> CM () - runRelayRequestOperation vr user uclId = - withWork_ a doWork (withStore' getNextPendingRelayRequest) $ + runRelayRequestOperation :: TM.TMap GroupId (TMVar (Weak ThreadId)) -> VersionRangeChat -> User -> Int64 -> CM () + runRelayRequestOperation delayThreads vr user uclId = + withWork_ a doWork getReadyRelayRequest $ \(groupId, rrd) -> do - ri <- asks $ reconnectInterval . agentConfig . config - withRetryInterval ri $ \_ loop -> do - liftIO $ waitWhileSuspended a - liftIO $ waitForUserNetwork a - processRelayRequest groupId rrd `catchAllErrors` retryTmpError loop groupId + ChatConfig {relayRequestExpiry} <- asks config + liftIO $ waitWhileSuspended a + liftIO $ waitForUserNetwork a + processRelayRequest groupId rrd `catchAllErrors` retryTmpError relayRequestExpiry groupId rrd where - retryTmpError :: CM () -> GroupId -> ChatError -> CM () - retryTmpError loop groupId = \case - ChatErrorAgent {agentError} | temporaryOrHostError agentError -> loop + getReadyRelayRequest :: CM (Either StoreError (Maybe (GroupId, RelayRequestData))) + getReadyRelayRequest = + withStore' getNextPendingRelayRequest >>= \case + Right (Just (groupId, rrd@RelayRequestData {reqExecuteAt})) -> do + currentTs <- liftIO getCurrentTime + let delay = diffUTCTime reqExecuteAt currentTs + if delay <= 1 + then pure $ Right (Just (groupId, rrd)) + else Right Nothing <$ scheduleRequest groupId delay + r -> pure r + scheduleRequest :: GroupId -> NominalDiffTime -> CM () + scheduleRequest groupId delay = do + v_ <- liftIO $ atomically $ + ifM + (isNothing <$> TM.lookup groupId delayThreads) + (newEmptyTMVar >>= \v -> TM.insert groupId v delayThreads $> Just v) + (pure Nothing) + forM_ v_ $ \v -> do + tId <- liftIO $ forkIO $ do + threadDelay' $ diffToMicroseconds delay + atomically $ TM.delete groupId delayThreads + void $ atomically $ tryPutTMVar doWork () + weakTId <- liftIO $ mkWeakThreadId tId + liftIO $ atomically $ putTMVar v weakTId + retryTmpError :: (Int, NominalDiffTime) -> GroupId -> RelayRequestData -> ChatError -> CM () + retryTmpError (retriesThreshold, ttl) groupId RelayRequestData {reqDelay, reqRetries, reqCreatedAt} = \case + ChatErrorAgent {agentError} | temporaryOrHostError agentError -> do + currentTs <- liftIO getCurrentTime + if reqRetries >= retriesThreshold && diffUTCTime currentTs reqCreatedAt >= ttl + then withStore' $ \db -> setRelayRequestErr db groupId "expired" + else do + ri <- asks $ relayRequestRetryInterval . config + let executeAt = addUTCTime (fromIntegral reqDelay / 1000000) currentTs + nextDelay = nextRetryDelay 0 reqDelay ri + withStore' $ \db -> updateRelayRequestRetries db groupId nextDelay executeAt e -> do withStore' $ \db -> setRelayRequestErr db groupId (tshow e) eToView e diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index d90429f58e..5800ab5bdd 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -119,6 +119,11 @@ checkChatType x = case testEquality (chatTypeI @c) (chatTypeI @c') of data GroupChatScope = GCSMemberSupport {groupMemberId_ :: Maybe GroupMemberId} -- Nothing means own conversation with support deriving (Eq, Show, Ord) +sendAsGroup' :: GroupInfo -> Maybe GroupChatScope -> Bool +sendAsGroup' gInfo@GroupInfo {membership} scope = case scope of + Nothing -> useRelays' gInfo && memberRole' membership == GROwner + Just (GCSMemberSupport _) -> False + data GroupChatScopeTag = GCSTMemberSupport_ deriving (Eq, Show) diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index b08cc5a991..2dc751d6bb 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -177,9 +177,16 @@ data CIContent (d :: MsgDirection) where deriving instance Show (CIContent d) -data E2EInfo = E2EInfo {pqEnabled :: Maybe PQEncryption} +-- stored in database, all changed must be backward compatible +data E2EInfo = E2EInfo {public :: Maybe Bool, pqEnabled :: Maybe PQEncryption} deriving (Eq, Show) +e2eInfoEncrypted :: Maybe PQEncryption -> E2EInfo +e2eInfoEncrypted pqEnabled = E2EInfo {public = Nothing, pqEnabled} + +e2eInfoGroup :: GroupInfo -> E2EInfo +e2eInfoGroup g = E2EInfo {public = if useRelays' g then Just True else Nothing, pqEnabled = Just PQEncOff} + ciMsgContent :: CIContent d -> Maybe MsgContent ciMsgContent = \case CISndMsgContent mc -> Just mc @@ -315,9 +322,14 @@ directE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of Nothing -> simpleE2EText groupE2EInfoToText :: E2EInfo -> Text -groupE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of - Just _ -> e2eInfoNoPQText - Nothing -> simpleE2EText +groupE2EInfoToText E2EInfo {pqEnabled, public} = case public of + Just True -> publicGroupNoE2EText + _ -> case pqEnabled of + Just _ -> e2eInfoNoPQText + Nothing -> simpleE2EText + +publicGroupNoE2EText :: Text +publicGroupNoE2EText = "This channel or group is NOT end-to-end encrypted." simpleE2EText :: Text simpleE2EText = "This conversation is protected by end-to-end encryption" diff --git a/src/Simplex/Chat/Operators/Presets.hs b/src/Simplex/Chat/Operators/Presets.hs index 23c9157121..53f31e005d 100644 --- a/src/Simplex/Chat/Operators/Presets.hs +++ b/src/Simplex/Chat/Operators/Presets.hs @@ -93,7 +93,8 @@ disabledSimplexChatSMPServers = simplexChatRelays :: [NewUserChatRelay] simplexChatRelays = [ presetChatRelay True (mkRelayProfile "SimpleX Chat Relay 1" $ Just simplexChatImage) ["simplex.im"] (either error id $ strDecode "https://smp5.simplex.im/r#Fp5RWXkiRFg-hgcDwC2v-MWnPfvEf42RgCqREntW0mw"), - presetChatRelay True (mkRelayProfile "SimpleX Chat Relay 2" $ Just simplexChatImage) ["simplex.im"] (either error id $ strDecode "https://smp6.simplex.im/r#_qlQfogHGDJ8MAF2wKmkglRBM-xHR142gDJstKiGRQQ") + presetChatRelay True (mkRelayProfile "SimpleX Chat Relay 2" $ Just simplexChatImage) ["simplex.im"] (either error id $ strDecode "https://smp6.simplex.im/r#_qlQfogHGDJ8MAF2wKmkglRBM-xHR142gDJstKiGRQQ"), + presetChatRelay True (mkRelayProfile "SimpleX Chat Relay 3" $ Just simplexChatImage) ["simplex.im"] (either error id $ strDecode "https://smp4.simplex.im/r#yxNOMJcry5jMTRPEBVtGBATYaKeoRIsZRBPIDLx7x6M") ] fluxSMPServers :: [NewUserServer 'PSMP] diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 1aadfb507e..cfe8e944a5 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -79,7 +79,7 @@ minRemoteCtrlVersion = AppVersion [6, 5, 0, 12] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 4, 6, 0] +minRemoteHostVersion = AppVersion [6, 5, 0, 12] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index c4e7210637..b375f77eb0 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1515,8 +1515,8 @@ setGroupInProgressDone db GroupInfo {groupId} = do "UPDATE groups SET creating_in_progress = 0, updated_at = ? WHERE group_id = ?" (currentTs, groupId) -createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> ExceptT StoreError IO (GroupInfo, GroupMember) -createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange = do +createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> ExceptT StoreError IO (GroupInfo, GroupMember) +createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay = do currentTs <- liftIO getCurrentTime -- Create group with placeholder profile let Profile {displayName = fromMemberLDN} = fromMemberProfile @@ -1532,7 +1532,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe } (groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just RSInvited) Nothing currentTs -- Store relay request data for recovery - liftIO $ setRelayRequestData_ groupId + liftIO $ setRelayRequestData_ groupId currentTs ownerMemberId <- insertOwner_ currentTs groupId let relayMember = MemberIdRole relayMemberId GRRelay -- TODO [member keys] should relays use member keys? @@ -1541,7 +1541,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe g <- getGroupInfo db vr user groupId pure (g, ownerMember) where - setRelayRequestData_ groupId = + setRelayRequestData_ groupId currentTs = DB.execute db [sql| @@ -1549,12 +1549,15 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe SET relay_request_inv_id = ?, relay_request_group_link = ?, relay_request_peer_chat_min_version = ?, - relay_request_peer_chat_max_version = ? + relay_request_peer_chat_max_version = ?, + relay_request_delay = ?, + relay_request_execute_at = ? WHERE group_id = ? |] - (Binary invId, groupLink, minVersion reqChatVRange, maxVersion reqChatVRange, groupId) + (Binary invId, groupLink, minVersion reqChatVRange, maxVersion reqChatVRange, initialDelay, currentTs, groupId) insertOwner_ currentTs groupId = do let MemberIdRole {memberId, memberRole} = fromMember + VersionRange minV maxV = reqChatVRange (localDisplayName, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs indexInGroup <- getUpdateNextIndexInGroup_ db groupId liftIO $ do @@ -1563,11 +1566,13 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe [sql| INSERT INTO group_members ( group_id, index_in_group, member_id, member_role, member_category, member_status, - user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, + peer_chat_min_version, peer_chat_max_version) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (groupId, indexInGroup, memberId, memberRole, GCHostMember, GSMemAccepted) :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs) + :. (minV, maxV) ) insertedRowId db @@ -2966,8 +2971,8 @@ createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {g where VersionRange minV maxV = vr -createLinkOwnerMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember -createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId ownerKey = do +createLinkOwnerMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe ContactId -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember +createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId} contactId_ memberId ownerKey = do currentTs <- liftIO getCurrentTime let memberProfile = profileFromName $ nameFromMemberId memberId (localDisplayName, profileId) <- createNewMemberProfile_ db user memberProfile currentTs @@ -2983,7 +2988,7 @@ createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (groupId, indexInGroup, memberId, GROwner, GCPreMember, GSMemUnknown, Binary B.empty, fromInvitedBy userContactId IBUnknown) - :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, ownerKey, currentTs, currentTs) + :. (userId, localDisplayName, contactId_, profileId, ownerKey, currentTs, currentTs) :. (minV, maxV) ) groupMemberId <- liftIO $ insertedRowId db diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 06efcdc17a..cdb461ea70 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -28,6 +28,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260108_chat_indices import Simplex.Chat.Store.Postgres.Migrations.M20260122_has_link import Simplex.Chat.Store.Postgres.Migrations.M20260222_chat_relays import Simplex.Chat.Store.Postgres.Migrations.M20260403_item_viewed +import Simplex.Chat.Store.Postgres.Migrations.M20260429_relay_request_retries import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -55,7 +56,8 @@ schemaMigrations = ("20260108_chat_indices", m20260108_chat_indices, Just down_m20260108_chat_indices), ("20260122_has_link", m20260122_has_link, Just down_m20260122_has_link), ("20260222_chat_relays", m20260222_chat_relays, Just down_m20260222_chat_relays), - ("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed) + ("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed), + ("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260429_relay_request_retries.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260429_relay_request_retries.hs new file mode 100644 index 0000000000..df9a2632f7 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260429_relay_request_retries.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20260429_relay_request_retries where + +import Data.Text (Text) +import Text.RawString.QQ (r) + +m20260429_relay_request_retries :: Text +m20260429_relay_request_retries = + [r| +ALTER TABLE groups ADD COLUMN relay_request_retries BIGINT NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN relay_request_delay BIGINT NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN relay_request_execute_at TIMESTAMPTZ NOT NULL DEFAULT '1970-01-01 00:00:00+00'; +|] + +down_m20260429_relay_request_retries :: Text +down_m20260429_relay_request_retries = + [r| +ALTER TABLE groups DROP COLUMN relay_request_retries; +ALTER TABLE groups DROP COLUMN relay_request_delay; +ALTER TABLE groups DROP COLUMN relay_request_execute_at; +|] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 41e6f0ed61..354c41eaaf 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -959,7 +959,10 @@ CREATE TABLE test_chat_schema.groups ( root_priv_key bytea, root_pub_key bytea, member_priv_key bytea, - public_member_count bigint + public_member_count bigint, + relay_request_retries bigint DEFAULT 0 NOT NULL, + relay_request_delay bigint DEFAULT 0 NOT NULL, + relay_request_execute_at timestamp with time zone DEFAULT '1970-01-01 01:00:00+01'::timestamp with time zone NOT NULL ); diff --git a/src/Simplex/Chat/Store/RelayRequests.hs b/src/Simplex/Chat/Store/RelayRequests.hs index 3858281878..2e590a1696 100644 --- a/src/Simplex/Chat/Store/RelayRequests.hs +++ b/src/Simplex/Chat/Store/RelayRequests.hs @@ -9,13 +9,15 @@ module Simplex.Chat.Store.RelayRequests ( hasPendingRelayRequests, getNextPendingRelayRequest, + updateRelayRequestRetries, setRelayRequestErr, ) where +import Data.Int (Int64) import Data.Maybe (fromMaybe) import Data.Text (Text) -import Data.Time.Clock (getCurrentTime) +import Data.Time.Clock (UTCTime, getCurrentTime) import Simplex.Chat.Store.Shared import Simplex.Chat.Types import Simplex.Chat.Types.Shared @@ -64,7 +66,7 @@ getNextPendingRelayRequest db = WHERE relay_own_status = ? AND relay_request_failed = 0 AND relay_request_err_reason IS NULL - ORDER BY group_id ASC + ORDER BY relay_request_execute_at ASC LIMIT 1 |] (Only RSInvited) @@ -76,18 +78,27 @@ getNextPendingRelayRequest db = [sql| SELECT relay_request_inv_id, relay_request_group_link, - relay_request_peer_chat_min_version, relay_request_peer_chat_max_version + relay_request_peer_chat_min_version, relay_request_peer_chat_max_version, + relay_request_delay, relay_request_retries, created_at, relay_request_execute_at FROM groups WHERE group_id = ? |] (Only groupId) where - toRelayRequestData :: (Maybe InvitationId, Maybe ShortLinkContact, Maybe VersionChat, Maybe VersionChat) -> Either StoreError (GroupId, RelayRequestData) + toRelayRequestData :: (Maybe InvitationId, Maybe ShortLinkContact, Maybe VersionChat, Maybe VersionChat, Int64, Int, UTCTime, UTCTime) -> Either StoreError (GroupId, RelayRequestData) toRelayRequestData = \case - (Just relayInvId, Just reqGroupLink, Just minV, Just maxV) -> - Right (groupId, RelayRequestData {relayInvId, reqGroupLink, reqChatVRange = fromMaybe (versionToRange maxV) $ safeVersionRange minV maxV}) + (Just relayInvId, Just reqGroupLink, Just minV, Just maxV, reqDelay, reqRetries, reqCreatedAt, reqExecuteAt) -> + Right (groupId, RelayRequestData {relayInvId, reqGroupLink, reqChatVRange = fromMaybe (versionToRange maxV) $ safeVersionRange minV maxV, reqDelay, reqRetries, reqCreatedAt, reqExecuteAt}) _ -> Left $ SEInternalError "missing relay request data" +updateRelayRequestRetries :: DB.Connection -> GroupId -> Int64 -> UTCTime -> IO () +updateRelayRequestRetries db groupId delay executeAt = do + currentTs <- getCurrentTime + DB.execute + db + "UPDATE groups SET relay_request_retries = relay_request_retries + 1, relay_request_delay = ?, relay_request_execute_at = ?, updated_at = ? WHERE group_id = ?" + (delay, executeAt, currentTs, groupId) + markRelayRequestFailed :: DB.Connection -> GroupId -> IO () markRelayRequestFailed db groupId = do currentTs <- getCurrentTime diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 607e0549b1..0ab8911ffd 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -151,6 +151,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260108_chat_indices import Simplex.Chat.Store.SQLite.Migrations.M20260122_has_link import Simplex.Chat.Store.SQLite.Migrations.M20260222_chat_relays import Simplex.Chat.Store.SQLite.Migrations.M20260403_item_viewed +import Simplex.Chat.Store.SQLite.Migrations.M20260429_relay_request_retries import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -301,7 +302,8 @@ schemaMigrations = ("20260108_chat_indices", m20260108_chat_indices, Just down_m20260108_chat_indices), ("20260122_has_link", m20260122_has_link, Just down_m20260122_has_link), ("20260222_chat_relays", m20260222_chat_relays, Just down_m20260222_chat_relays), - ("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed) + ("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed), + ("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260429_relay_request_retries.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260429_relay_request_retries.hs new file mode 100644 index 0000000000..fea87f4baf --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260429_relay_request_retries.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20260429_relay_request_retries where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20260429_relay_request_retries :: Query +m20260429_relay_request_retries = + [sql| +ALTER TABLE groups ADD COLUMN relay_request_retries INTEGER NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN relay_request_delay INTEGER NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN relay_request_execute_at TEXT NOT NULL DEFAULT '1970-01-01 00:00:00'; +|] + +down_m20260429_relay_request_retries :: Query +down_m20260429_relay_request_retries = + [sql| +ALTER TABLE groups DROP COLUMN relay_request_retries; +ALTER TABLE groups DROP COLUMN relay_request_delay; +ALTER TABLE groups DROP COLUMN relay_request_execute_at; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 366074acc2..7a226bf78a 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -523,8 +523,9 @@ SEARCH users USING COVERING INDEX sqlite_autoindex_users_1 (contact_id=?) Query: INSERT INTO group_members ( group_id, index_in_group, member_id, member_role, member_category, member_status, - user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, + peer_chat_min_version, peer_chat_max_version) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: SEARCH group_relays USING COVERING INDEX idx_group_relays_group_member_id (group_member_id=?) @@ -694,7 +695,8 @@ SEARCH delivery_jobs USING INTEGER PRIMARY KEY (rowid=?) Query: SELECT relay_request_inv_id, relay_request_group_link, - relay_request_peer_chat_min_version, relay_request_peer_chat_max_version + relay_request_peer_chat_min_version, relay_request_peer_chat_max_version, + relay_request_delay, relay_request_retries, created_at, relay_request_execute_at FROM groups WHERE group_id = ? @@ -992,11 +994,12 @@ Query: WHERE relay_own_status = ? AND relay_request_failed = 0 AND relay_request_err_reason IS NULL - ORDER BY group_id ASC + ORDER BY relay_request_execute_at ASC LIMIT 1 Plan: SCAN groups +USE TEMP B-TREE FOR ORDER BY Query: SELECT i.chat_item_id @@ -1774,7 +1777,9 @@ Query: SET relay_request_inv_id = ?, relay_request_group_link = ?, relay_request_peer_chat_min_version = ?, - relay_request_peer_chat_max_version = ? + relay_request_peer_chat_max_version = ?, + relay_request_delay = ?, + relay_request_execute_at = ? WHERE group_id = ? Plan: diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 36f373f193..f081238908 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -173,7 +173,10 @@ CREATE TABLE groups( root_priv_key BLOB, root_pub_key BLOB, member_priv_key BLOB, - public_member_count INTEGER, -- received + public_member_count INTEGER, + relay_request_retries INTEGER NOT NULL DEFAULT 0, + relay_request_delay INTEGER NOT NULL DEFAULT 0, + relay_request_execute_at TEXT NOT NULL DEFAULT '1970-01-01 00:00:00', -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 6ddc411fff..b068e6b679 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -494,9 +494,6 @@ data GroupInfo = GroupInfo useRelays' :: GroupInfo -> Bool useRelays' GroupInfo {useRelays} = isTrue useRelays -sendAsGroup' :: GroupInfo -> Bool -sendAsGroup' gInfo@GroupInfo {membership} = useRelays' gInfo && memberRole' membership == GROwner - groupId' :: GroupInfo -> GroupId groupId' GroupInfo {groupId} = groupId @@ -769,15 +766,18 @@ fromLocalProfile LocalProfile {displayName, fullName, shortDescr, image, contact data GroupType = GTChannel + | GTGroup | GTUnknown Text deriving (Eq, Show) instance TextEncoding GroupType where textEncode = \case GTChannel -> "channel" + GTGroup -> "group" GTUnknown tag -> tag textDecode s = Just $ case s of "channel" -> GTChannel + "group" -> GTGroup tag -> GTUnknown tag instance FromField GroupType where fromField = fromTextField_ textDecode @@ -1045,7 +1045,11 @@ data GroupMember = GroupMember data RelayRequestData = RelayRequestData { relayInvId :: InvitationId, reqGroupLink :: ShortLinkContact, - reqChatVRange :: VersionRangeChat + reqChatVRange :: VersionRangeChat, + reqDelay :: Int64, + reqRetries :: Int, + reqCreatedAt :: UTCTime, + reqExecuteAt :: UTCTime } deriving (Eq, Show) diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index c02d2b8433..be189379c9 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -176,6 +176,7 @@ data GroupFeature | GFSimplexLinks | GFReports | GFHistory + | GFSupport | GFSessions | GFComments deriving (Show) @@ -190,6 +191,7 @@ data SGroupFeature (f :: GroupFeature) where SGFSimplexLinks :: SGroupFeature 'GFSimplexLinks SGFReports :: SGroupFeature 'GFReports SGFHistory :: SGroupFeature 'GFHistory + SGFSupport :: SGroupFeature 'GFSupport SGFSessions :: SGroupFeature 'GFSessions SGFComments :: SGroupFeature 'GFComments @@ -218,6 +220,7 @@ groupFeatureNameText = \case GFSimplexLinks -> "SimpleX links" GFReports -> "Member reports" GFHistory -> "Recent history" + GFSupport -> "Chat with admins" GFSessions -> "Chat sessions" GFComments -> "Comments" @@ -248,11 +251,12 @@ allGroupFeatures = AGF SGFFiles, AGF SGFSimplexLinks, AGF SGFReports, - AGF SGFHistory + AGF SGFHistory, + AGF SGFSupport ] groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) -groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, sessions, comments} = case f of +groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of SGFTimedMessages -> timedMessages SGFDirectMessages -> directMessages SGFFullDelete -> fullDelete @@ -262,6 +266,7 @@ groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reac SGFSimplexLinks -> simplexLinks SGFReports -> reports SGFHistory -> history + SGFSupport -> support SGFSessions -> sessions SGFComments -> comments @@ -276,6 +281,7 @@ toGroupFeature = \case SGFSimplexLinks -> GFSimplexLinks SGFReports -> GFReports SGFHistory -> GFHistory + SGFSupport -> GFSupport SGFSessions -> GFSessions SGFComments -> GFComments @@ -289,7 +295,7 @@ instance GroupPreferenceI (Maybe GroupPreferences) where getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) instance GroupPreferenceI FullGroupPreferences where - getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, sessions, comments} = case f of + getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of SGFTimedMessages -> timedMessages SGFDirectMessages -> directMessages SGFFullDelete -> fullDelete @@ -299,6 +305,7 @@ instance GroupPreferenceI FullGroupPreferences where SGFSimplexLinks -> simplexLinks SGFReports -> reports SGFHistory -> history + SGFSupport -> support SGFSessions -> sessions SGFComments -> comments {-# INLINE getGroupPreference #-} @@ -314,6 +321,7 @@ data GroupPreferences = GroupPreferences simplexLinks :: Maybe SimplexLinksGroupPreference, reports :: Maybe ReportsGroupPreference, history :: Maybe HistoryGroupPreference, + support :: Maybe SupportGroupPreference, sessions :: Maybe SessionsGroupPreference, comments :: Maybe CommentsGroupPreference, commands :: Maybe [ChatBotCommand] @@ -365,6 +373,7 @@ setGroupPreference_ f pref prefs = SGFSimplexLinks -> prefs {simplexLinks = pref} SGFReports -> prefs {reports = pref} SGFHistory -> prefs {history = pref} + SGFSupport -> prefs {support = pref} SGFSessions -> prefs {sessions = pref} SGFComments -> prefs {comments = pref} @@ -408,6 +417,7 @@ data FullGroupPreferences = FullGroupPreferences simplexLinks :: SimplexLinksGroupPreference, reports :: ReportsGroupPreference, history :: HistoryGroupPreference, + support :: SupportGroupPreference, sessions :: SessionsGroupPreference, comments :: CommentsGroupPreference, commands :: ListDef ChatBotCommand @@ -478,13 +488,14 @@ defaultGroupPrefs = simplexLinks = SimplexLinksGroupPreference {enable = FEOn, role = Nothing}, reports = ReportsGroupPreference {enable = FEOn}, history = HistoryGroupPreference {enable = FEOff}, + support = SupportGroupPreference {enable = FEOn}, sessions = SessionsGroupPreference {enable = FEOff, role = Nothing}, comments = CommentsGroupPreference {enable = FEOff, duration = Nothing}, commands = ListDef [] } emptyGroupPrefs :: GroupPreferences -emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing businessGroupPrefs :: Preferences -> GroupPreferences businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice, files, sessions, commands} = @@ -515,6 +526,7 @@ defaultBusinessGroupPrefs = simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing, reports = Just $ ReportsGroupPreference FEOff, history = Just $ HistoryGroupPreference FEOn, + support = Just $ SupportGroupPreference FEOn, sessions = Just $ SessionsGroupPreference FEOn Nothing, comments = Just $ CommentsGroupPreference FEOff Nothing, commands = Nothing @@ -647,6 +659,10 @@ data HistoryGroupPreference = HistoryGroupPreference {enable :: GroupFeatureEnabled} deriving (Eq, Show) +data SupportGroupPreference = SupportGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show) + data SessionsGroupPreference = SessionsGroupPreference {enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole} deriving (Eq, Show) @@ -699,6 +715,9 @@ instance HasField "enable" ReportsGroupPreference GroupFeatureEnabled where instance HasField "enable" HistoryGroupPreference GroupFeatureEnabled where hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable) +instance HasField "enable" SupportGroupPreference GroupFeatureEnabled where + hasField p@SupportGroupPreference {enable} = (\e -> p {enable = e}, enable) + instance HasField "enable" SessionsGroupPreference GroupFeatureEnabled where hasField p@SessionsGroupPreference {enable} = (\e -> p {enable = e}, enable) @@ -759,6 +778,12 @@ instance GroupFeatureI 'GFHistory where groupPrefParam _ = Nothing groupPrefRole _ = Nothing +instance GroupFeatureI 'GFSupport where + type GroupFeaturePreference 'GFSupport = SupportGroupPreference + sGroupFeature = SGFSupport + groupPrefParam _ = Nothing + groupPrefRole _ = Nothing + instance GroupFeatureI 'GFSessions where type GroupFeaturePreference 'GFSessions = SessionsGroupPreference sGroupFeature = SGFSessions @@ -781,6 +806,8 @@ instance GroupFeatureNoRoleI 'GFReports instance GroupFeatureNoRoleI 'GFHistory +instance GroupFeatureNoRoleI 'GFSupport + instance GroupFeatureNoRoleI 'GFComments instance HasField "role" DirectMessagesGroupPreference (Maybe GroupMemberRole) where @@ -973,6 +1000,7 @@ mergeGroupPreferences groupPreferences = simplexLinks = pref SGFSimplexLinks, reports = pref SGFReports, history = pref SGFHistory, + support = pref SGFSupport, sessions = pref SGFSessions, comments = pref SGFComments, commands = ListDef $ fromMaybe [] $ groupPreferences >>= commands_ @@ -993,6 +1021,7 @@ toGroupPreferences groupPreferences@FullGroupPreferences {commands = ListDef cmd simplexLinks = pref SGFSimplexLinks, reports = pref SGFReports, history = pref SGFHistory, + support = pref SGFSupport, sessions = pref SGFSessions, comments = pref SGFComments, commands = Just cmds @@ -1123,11 +1152,13 @@ $(J.deriveJSON defaultJSON ''ReportsGroupPreference) $(J.deriveJSON defaultJSON ''HistoryGroupPreference) -$(J.deriveToJSON defaultJSON ''SessionsGroupPreference) +$(J.deriveToJSON defaultJSON ''SupportGroupPreference) -instance FromJSON SessionsGroupPreference where - parseJSON v = $(J.mkParseJSON defaultJSON ''SessionsGroupPreference) v - omittedField = Just SessionsGroupPreference {enable = FEOff, role = Nothing} +instance FromJSON SupportGroupPreference where + parseJSON v = $(J.mkParseJSON defaultJSON ''SupportGroupPreference) v + omittedField = Just SupportGroupPreference {enable = FEOn} + +$(J.deriveJSON defaultJSON ''SessionsGroupPreference) $(J.deriveToJSON defaultJSON ''CommentsGroupPreference) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index ce0f55cf01..98eb811f5a 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2103,7 +2103,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case GLPConnectingConfirmReconnect -> [grpLink "connecting, allowed to reconnect"] GLPConnectingProhibit Nothing -> [grpLink "connecting"] GLPConnectingProhibit (Just g) -> connecting g - GLPKnown g@GroupInfo {preparedGroup, membership = m} -> case preparedGroup of + GLPKnown g@GroupInfo {preparedGroup, membership = m} _ _ _ -> case preparedGroup of Just PreparedGroup {connLinkStartedConnection} -> case memberStatus m of GSMemUnknown | connLinkStartedConnection -> connecting g diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 852e4cf4c4..68acec0493 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -7,7 +7,9 @@ module Bots.DirectoryTests where import ChatClient +import ChatTests.ChatRelays (withRelay) import ChatTests.DBUtils +import ChatTests.Groups (memberJoinChannel, prepareChannel1Relay) import ChatTests.Utils import Control.Concurrent (forkIO, killThread, threadDelay) import Control.Exception (finally) @@ -42,6 +44,7 @@ directoryServiceTests = do it "should support group names with spaces" testGroupNameWithSpaces it "should return more groups in search, all and recent groups" testSearchGroups it "should invite to owners' group if specified" testInviteToOwnersGroup + it "should re-invite owner who left owners' group" testInviteOwnerAfterLeavingOwnersGroup describe "de-listing the group" $ do it "should de-list if owner leaves the group" testDelistedOwnerLeaves it "should de-list if owner is removed from the group" testDelistedOwnerRemoved @@ -85,6 +88,13 @@ directoryServiceTests = do describe "help commands" $ do it "should not list audio command" testHelpNoAudio it "should reject audio command in DM" testAudioCommandInDM + describe "public group registration" $ do + it "should register channel via shared link card" testRegisterChannelViaCard + it "should suggest share via chat when link sent as text" testLinkAsTextSearch + it "should reject card shared by non-owner" testNonOwnerSharesCard + it "should delete channel registration and leave" testDeleteChannelRegistration + it "should handle re-registration when already listed" testReregistrationAlreadyListed + it "should update subscriber count periodically" testLinkCheckUpdatesCount directoryProfile :: Profile directoryProfile = Profile {displayName = "SimpleX Directory", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing} @@ -120,6 +130,7 @@ mkDirectoryOpts TestParams {tmpPath = ps} superUsers ownersGroup webFolder = runCLI = False, searchResults = 3, webFolder, + linkCheckInterval = 0, testing = True } @@ -567,6 +578,32 @@ testInviteToOwnersGroup ps = registerGroupId superUser bob "security" "Security" 3 2 superUser <## "Owner is already a member of owners' group" +testInviteOwnerAfterLeavingOwnersGroup :: HasCallStack => TestParams -> IO () +testInviteOwnerAfterLeavingOwnersGroup ps = + withDirectoryServiceCfgOwnersGroup ps testCfg True Nothing $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> do + bob `connectVia` dsLink + registerGroupId superUser bob "privacy" "Privacy" 2 1 + bob <## "#owners: 'SimpleX Directory' invites you to join the group as member" + bob <## "use /j owners to accept" + superUser <## "Invited @bob, the owner of the group ID 2 (privacy) to owners' group owners" + bob ##> "/j owners" + bob <## "#owners: you joined the group" + bob <## "#owners: member alice (Alice) is connected" + superUser <## "#owners: 'SimpleX Directory' added bob (Bob) to the group (connecting...)" + superUser <## "#owners: new member bob is connected" + -- owner leaves owners' group; GroupMember row keeps status GSMemLeft + leaveGroup "owners" bob + superUser <## "#owners: bob left the group" + -- owners' group has no GroupReg, so directory service notifies admins on contact left + superUser <# "'SimpleX Directory'> Error: contact left, group: 1 owners, group registration not found" + -- super-user re-invites via /invite — must send a fresh invitation, not "already a member" + superUser #> "@'SimpleX Directory' /invite 2:privacy" + superUser <# "'SimpleX Directory'> > /invite 2:privacy" + superUser <## " you invited @bob, the owner of the group ID 2 (privacy) to owners' group owners" + bob <## "#owners_1: 'SimpleX Directory' invites you to join the group as member" + bob <## "use /j owners_1 to accept" + testDelistedOwnerLeaves :: HasCallStack => TestParams -> IO () testDelistedOwnerLeaves ps = withDirectoryService ps $ \superUser dsLink -> @@ -1771,7 +1808,7 @@ u `connectVia` dsLink = do u .<# "> Welcome to SimpleX Directory!" u <## "" u <## "🔍 Send search string to find groups - try security." - u <## "/help - how to submit your group." + u <## "/help - how to submit your group or channel." u <## "/new - recent groups." u <## "" u <## "[Directory rules](https://simplex.chat/docs/directory.html)." @@ -1922,7 +1959,7 @@ testHelpNoAudio ps = -- commands help should not mention /audio bob #> "@'SimpleX Directory' /help commands" bob <# "'SimpleX Directory'> /'help commands' - receive this help message." - bob <## "/help - how to register your group to be added to directory." + bob <## "/help - how to register your group or channel to be added to directory." bob <## "/list - list the groups you registered." bob <## "`/role ` - view and set default member role for your group." bob <## "`/filter ` - view and set spam filter settings for group." @@ -1940,6 +1977,266 @@ testAudioCommandInDM ps = bob <# "'SimpleX Directory'> > /audio" bob <## " Unknown command" +testRegisterChannelViaCard :: HasCallStack => TestParams -> IO () +testRegisterChannelViaCard ps = + withDirectoryServiceCfg ps testCfg $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + -- bob connects to directory service first + bob `connectVia` dsLink + -- bob creates a channel with a relay + (_shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + -- bob shares the channel card with directory bot + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + -- directory bot validates and joins via relay + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + -- owner sends a message to trigger member introduction + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "1 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + -- superuser approves + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- owner updates channel profile, triggering re-approval + bob ##> "/gp news news News and Updates" + bob <## "description changed to: News and Updates" + bob <# "'SimpleX Directory'> The channel ID 1 (news) is updated." + bob <## "It is hidden from the directory until approved." + relay <## "bob updated group #news: (signed)" + relay <## "description changed to: News and Updates" + superUser <# "'SimpleX Directory'> The channel ID 1 (news) is updated." + superUser <# ("'SimpleX Directory'> bob submitted the channel ID 1:") + superUser <## "news (News and Updates)" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "2 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + -- re-approve after profile update + let approve2 = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve2) + superUser <# ("'SimpleX Directory'> > " <> approve2) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- owner leaves channel, triggering de-listing and bot leaving + bob ##> "/leave #news" + concurrentlyN_ + [ do + bob <## "#news: you left the group" + bob <## "use /d #news to delete the group", + relay <## "#news: bob left the group (signed)" + ] + bob <# "'SimpleX Directory'> You left the channel ID 1 (news)." + bob <## "" + bob <## "The channel is no longer listed in the directory." + superUser <# "'SimpleX Directory'> The channel ID 1 (news) is de-listed (channel owner left)." + relay <## "#news: 'SimpleX Directory' left the group (signed)" + +testLinkAsTextSearch :: HasCallStack => TestParams -> IO () +testLinkAsTextSearch ps = + withDirectoryServiceCfg ps testCfg $ \_superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + bob `connectVia` dsLink + (shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + bob #> ("@'SimpleX Directory' " <> shortLink) + bob <# ("'SimpleX Directory'> > " <> shortLink) + bob <## " No groups found." + bob <## "To register a group or a channel, please use \"Share via chat\" feature." + +testNonOwnerSharesCard :: HasCallStack => TestParams -> IO () +testNonOwnerSharesCard ps = + withDirectoryServiceCfg ps testCfg $ \_superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> + withNewTestChatCfg ps testCfg "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + cath `connectVia` dsLink + (shortLink, fullLink) <- prepareChannel1Relay "news" bob relay + memberJoinChannel "news" [relay] [bob] shortLink fullLink cath + cath ##> "/share chat #news @'SimpleX Directory'" + cath <# "@'SimpleX Directory' link to join channel #news:" + _ <- getTermLine cath -- short link + cath <# "'SimpleX Directory'> To add a channel to directory you must be the owner." + +testDeleteChannelRegistration :: HasCallStack => TestParams -> IO () +testDeleteChannelRegistration ps = + withDirectoryServiceCfg ps testCfg $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + bob `connectVia` dsLink + (_shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "1 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- owner deletes registration + bob #> "@'SimpleX Directory' /delete 1:news" + bob + <### + [ WithTime "'SimpleX Directory'> > /delete 1:news", + " Your channel news is deleted from the directory", + "#news: 'SimpleX Directory_1' left the group (signed)" + ] + relay <## "#news: 'SimpleX Directory' left the group (signed)" + +testReregistrationAlreadyListed :: HasCallStack => TestParams -> IO () +testReregistrationAlreadyListed ps = + withDirectoryServiceCfg ps testCfg $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + bob `connectVia` dsLink + (_shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + -- register and approve + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "1 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- search finds the channel with its link + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "1 subscribers" + -- owner re-shares card while already listed + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Channel is already listed in the directory." + +testLinkCheckUpdatesCount :: HasCallStack => TestParams -> IO () +testLinkCheckUpdatesCount ps = do + dsLink <- + withNewTestChatCfg ps testCfg serviceDbPrefix directoryProfile $ \ds -> + withNewTestChatCfg ps testCfg "super_user" aliceProfile $ \superUser -> do + connectUsers ds superUser + ds ##> "/ad" + getContactLink ds True + let opts = (mkDirectoryOpts ps [KnownContact 2 "alice"] Nothing Nothing) {linkCheckInterval = 1} + runDirectory testCfg opts $ + withTestChatCfg ps testCfg "super_user" $ \superUser -> do + superUser <## "subscribed 1 connections on server localhost" + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> + withNewTestChatCfg ps testCfg "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + (shortLink, fullLink) <- prepareChannel1Relay "news" bob relay + -- register and approve + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "1 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- link check updates count (bot joined) + threadDelay 1000000 + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "2 subscribers" + -- second subscriber joins + memberJoinChannel "news" [relay] [bob] shortLink fullLink cath + -- link check updates count again + threadDelay 1000000 + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "3 subscribers" + testGetCaptchaStr :: HasCallStack => TestParams -> IO () testGetCaptchaStr _ps = do s0 <- getCaptchaStr 0 "" diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 714aa0c0ed..279a09e718 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -170,7 +170,7 @@ termSettings :: VirtualTerminalSettings termSettings = VirtualTerminalSettings { virtualType = "xterm", - virtualWindowSize = pure C.Size {height = 20, width = 6000}, + virtualWindowSize = pure C.Size {height = 24, width = 6000}, virtualEvent = retry, virtualInterrupt = retry } diff --git a/tests/ChatTests/ChatList.hs b/tests/ChatTests/ChatList.hs index 14d48dbf60..889915a6e8 100644 --- a/tests/ChatTests/ChatList.hs +++ b/tests/ChatTests/ChatList.hs @@ -200,14 +200,14 @@ testPaginationAllChatTypes = ts7 <- iso8601Show <$> getCurrentTime - getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] - getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on")] + getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] + getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on")] getChats_ alice ("after=" <> ts2 <> " count=2") [(":3", ""), ("@cath", "Audio/video calls: enabled")] - getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", "Recent history: on"), (":3", "")] - getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", "")] + getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", "Chat with admins: on"), (":3", "")] + getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", "")] getChats_ alice ("before=" <> ts4 <> " count=10") [(":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] - getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] - getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] + getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] + getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] getChats_ alice ("after=" <> ts7 <> " count=10") [] getChats_ alice ("before=" <> ts1 <> " count=10") [] @@ -219,11 +219,11 @@ testPaginationAllChatTypes = alice ##> "/_settings #1 {\"enableNtfs\":\"all\",\"favorite\":true}" alice <## "ok" - getChats_ alice queryFavorite [("#team", "Recent history: on"), ("@bob", "hey")] + getChats_ alice queryFavorite [("#team", "Chat with admins: on"), ("@bob", "hey")] getChats_ alice ("before=" <> ts4 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", "Recent history: on")] + getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", "Chat with admins: on")] getChats_ alice ("after=" <> ts1 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", "Recent history: on")] + getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", "Chat with admins: on")] let queryUnread = "{\"type\": \"filters\", \"favorite\": false, \"unread\": true}" diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 880c1373e9..fb94194561 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -19,7 +19,7 @@ import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Library.Internal (roundedFDCount) import Simplex.Chat.Mobile.File import Simplex.Chat.Options (ChatOpts (..)) -import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..)) +import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), XFTPStoreConfig (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Encoding.String import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist, getFileSize) @@ -940,7 +940,7 @@ testXFTPRcvError ps = do alice <## "completed uploading file 1 (test.pdf) for bob" -- server is up w/t store log - file reception should fail - withXFTPServer' xftpServerConfig {storeLogFile = Nothing} $ do + withXFTPServer' xftpServerConfig {serverStoreCfg = XSCMemory Nothing, storeLogFile = Nothing} $ do withTestChat ps "bob" $ \bob -> do bob <## "subscribed 1 connections on server localhost" bob ##> "/fr 1 ./tests/tmp" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 1a1bc4b82f..21bf6cb65a 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -28,6 +28,7 @@ import Simplex.Chat.Controller (ChatConfig (..), ChatHooks (..), defaultChatHook import Simplex.Chat.Library.Internal (uniqueMsgMentions, updatedMentionNames) import Simplex.Chat.Markdown (parseMaybeMarkdownList) import Simplex.Chat.Messages (CIMention (..), CIMentionMember (..), ChatItemId) +import Simplex.Chat.Messages.CIContent (publicGroupNoE2EText) import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgMention (..), MsgContent (..), msgContentText) import Simplex.Chat.Types @@ -230,9 +231,10 @@ chatGroupTests = do it "should remove support chat with member when member is removed" testScopedSupportMemberRemoved it "should remove support chat with member when user removes member" testScopedSupportUserRemovesMember it "should remove support chat with member when member leaves" testScopedSupportMemberLeaves + it "should respect support preference in group" testSupportPreferenceGroup + it "should respect support preference in channel" testSupportPreferenceChannel -- TODO [relays] add tests for channels -- TODO - tests with delivery loop over members restored after restart - -- TODO - delivery in support scopes inside channels -- TODO - connect plans for relay groups -- TODO - cancellation on failure to create relay group (for owner) -- TODO - async retry connecting to relay (for members) @@ -266,6 +268,7 @@ chatGroupTests = do it "owner should update profile in channel (signed)" testChannelOwnerProfileUpdate it "subscriber should update profile in channel (signed)" testChannelSubscriberProfileUpdate it "should report relay results when one relay deleted its address" testChannelCreateDeletedRelay + it "should deliver support scope messages via relay" testChannelSupportScope describe "channel message operations" $ do it "should update channel message" testChannelMessageUpdate it "should delete channel message" testChannelMessageDelete @@ -456,7 +459,7 @@ testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice forM_ ([1 .. 10] :: [Int]) $ \n -> bob <# ("#team alice> " <> show n) -- All messages are unread for bob, should return area around unread - bob #$> ("/_get chat #1 initial=2", chat, [(0, "Recent history: on"), (0, "connected"), (0, "1"), (0, "2"), (0, "3")]) + bob #$> ("/_get chat #1 initial=2", chat, [(0, "Chat with admins: on"), (0, "connected"), (0, "1"), (0, "2"), (0, "3")]) -- Read next 2 items let itemIds = intercalate "," $ map groupItemId [1 .. 2] @@ -645,7 +648,7 @@ testGroup2 = ] dan <##> alice -- show last messages - alice ##> "/t #club 20" + alice ##> "/t #club 21" alice -- these strings are expected in any order because of sorting by time and rounding of time for sent <##? ( map (ConsoleString . ("#club " <> )) groupFeatureStrs @@ -1666,6 +1669,7 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile alice <## "SimpleX links: on" alice <## "Member reports: on" alice <## "Recent history: on" + alice <## "Chat with admins: on" bobAddedDan :: HasCallStack => TestCC -> IO () bobAddedDan cc = do cc <## "#team: bob added dan (Daniel) to the group (connecting...)" @@ -8399,6 +8403,120 @@ testScopedSupportMemberLeaves = { markRead = False } +testSupportPreferenceGroup :: HasCallStack => TestParams -> IO () +testSupportPreferenceGroup = + testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do + createGroup3' "team" alice (bob, GRMember) (cath, GRMember) + + threadDelay 1000000 + + -- support enabled by default, bob sends to support + bob #> "#team (support) hello" + alice <# "#team (support: bob) bob> hello" + + -- alice replies + alice #> "#team (support: bob) hi" + bob <# "#team (support) alice> hi" + + -- alice disables support + alice ##> "/set support #team off" + alice <## "updated group preferences:" + alice <## "Chat with admins: off" + concurrentlyN_ + [ do + bob <## "alice updated group #team:" + bob <## "updated group preferences:" + bob <## "Chat with admins: off", + do + cath <## "alice updated group #team:" + cath <## "updated group preferences:" + cath <## "Chat with admins: off" + ] + + threadDelay 500000 + + -- cath can't send support (no existing chat) + cath ##> "#team (support) hey" + cath <## "bad chat command: feature not allowed Chat with admins" + + -- alice can't send to cath's support (no existing chat) + alice ##> "#team (support: cath) hey" + alice <## "bad chat command: feature not allowed Chat with admins" + + -- bob can still send (existing chat) + bob #> "#team (support) still here" + alice <# "#team (support: bob) bob> still here" + + -- alice can still send to bob (existing chat) + alice #> "#team (support: bob) yes" + bob <# "#team (support) alice> yes" + +testSupportPreferenceChannel :: HasCallStack => TestParams -> IO () +testSupportPreferenceChannel ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "relay" relayProfile $ \relay -> + withNewTestChat ps "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> do + (shortLink, fullLink) <- prepareChannel1Relay "team" alice relay + memberJoinChannel "team" [relay] [alice] shortLink fullLink bob + memberJoinChannel "team" [relay] [alice] shortLink fullLink cath + + threadDelay 1000000 + + alice ##> "/set support #team on" + alice <## "updated group preferences:" + alice <## "Chat with admins: on" + toggledSupport relay "alice" "team" "on" + concurrentlyN_ + [ toggledSupport bob "alice" "team" "on", + toggledSupport cath "alice" "team" "on" + ] + + -- support enabled by default, bob sends to support + bob #> "#team (support) hello" + relay <# "#team (support: bob) bob> hello" + alice <# "#team (support: bob) bob> hello [>>]" + + -- alice replies + alice #> "#team (support: bob) hi" + relay <# "#team (support: bob) alice> hi" + bob <# "#team (support) alice> hi [>>]" + + -- alice disables support + + threadDelay 1000000 + + alice ##> "/set support #team off" + alice <## "updated group preferences:" + alice <## "Chat with admins: off" + toggledSupport relay "alice" "team" "off" + concurrentlyN_ + [ toggledSupport bob "alice" "team" "off", + toggledSupport cath "alice" "team" "off" + ] + + threadDelay 500000 + + -- cath can't send support (no existing chat) + cath ##> "#team (support) hey" + cath <## "bad chat command: feature not allowed Chat with admins" + alice ##> "#team (support: cath) hey too" + alice <## "bad chat command: feature not allowed Chat with admins" + + -- bob can still send (existing chat) + bob #> "#team (support) still here" + concurrentlyN_ + [ relay <# "#team (support: bob) bob> still here", + alice <# "#team (support: bob) bob> still here [>>]" + ] + + -- alice can still send to bob (existing chat) + alice #> "#team (support: bob) yes" + concurrentlyN_ + [ relay <# "#team (support: bob) alice> yes", + bob <# "#team (support) alice> yes [>>]" + ] + testChannels1RelayDeliver :: HasCallStack => TestParams -> IO () testChannels1RelayDeliver ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do @@ -8857,7 +8975,7 @@ testChannelLinkAfterWelcomeUpdate ps = shortLink' `shouldBe` shortLink fullLink' `shouldBe` fullLink memberJoinChannel "team" [bob] [alice] shortLink' fullLink' dan - dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, e2eeInfoNoPQStr), (0, "connected")]) + dan #$> ("/_get chat #1 count=100", chat, channelFeaturesNoE2E <> [(0, "welcome to team"), (0, T.unpack publicGroupNoE2EText), (0, "connected")]) alice #> "#team hi" bob <# "#team> hi" @@ -9472,6 +9590,25 @@ testChannelSubscriberProfileUpdate ps = withNewTestChat ps "eve" eveProfile $ \eve -> do createChannel1Relay "team" alice bob cath dan eve + -- enable support and create support chat for cath (but not dan) + threadDelay 1000000 + alice ##> "/set support #team on" + alice <## "updated group preferences:" + alice <## "Chat with admins: on" + toggledSupport bob "alice" "team" "on" + concurrentlyN_ + [ toggledSupport cath "alice" "team" "on", + toggledSupport dan "alice" "team" "on", + toggledSupport eve "alice" "team" "on" + ] + + threadDelay 1000000 + alice #> "#team (support: cath) welcome" + bob <# "#team (support: cath) alice> welcome" + cath <# "#team (support) alice> welcome [>>]" + (dan "#team hello from cath" @@ -9487,6 +9624,7 @@ testChannelSubscriberProfileUpdate ps = ] -- known subscriber updates profile (XInfo signed) + -- cath has support chat -> profile update created in support scope threadDelay 1000000 cath ##> "/_profile 1 {\"displayName\": \"kate\", \"fullName\": \"\"}" cath <## "user profile is changed to kate (your 0 contacts are notified)" @@ -9497,9 +9635,12 @@ testChannelSubscriberProfileUpdate ps = dan <# "#team kate> hello from kate [>>]", eve <# "#team kate> hello from kate [>>]" ] - -- profile update items on alice and bob (owner/relay, signed) - alice #$> ("/_get chat #1 count=2", chat, [(0, "updated profile (signed)"), (0, "hello from kate")]) - bob #$> ("/_get chat #1 count=2", chat, [(0, "updated profile (signed)"), (0, "hello from kate")]) + -- no profile update items in main scope on alice and bob + alice #$> ("/_get chat #1 count=2", chat, [(0, "hello from cath"), (0, "hello from kate")]) + bob #$> ("/_get chat #1 count=2", chat, [(0, "hello from cath"), (0, "hello from kate")]) + -- profile update items in cath's support scope on alice and bob + alice #$> ("/_get chat #1(_support:3) count=1", chat, [(0, "updated profile (signed)")]) + bob #$> ("/_get chat #1(_support:3) count=1", chat, [(0, "updated profile (signed)")]) -- no profile update items on dan and eve (subscriber-to-subscriber muted) dan #$> ("/_get chat #1 count=2", chat, [(0, "hello from cath"), (0, "hello from kate")]) eve #$> ("/_get chat #1 count=2", chat, [(0, "hello from cath"), (0, "hello from kate")]) @@ -9512,6 +9653,7 @@ testChannelSubscriberProfileUpdate ps = eve `hasContactProfiles` ["alice", "bob", "kate", "eve"] -- previously silent subscriber updates profile + -- dan has no support chat -> no profile update item created threadDelay 1000000 dan ##> "/_profile 1 {\"displayName\": \"dave\", \"fullName\": \"\"}" dan <## "user profile is changed to dave (your 0 contacts are notified)" @@ -9526,14 +9668,18 @@ testChannelSubscriberProfileUpdate ps = cath <## "#team: bob forwarded a message from an unknown member, creating unknown member record dave" cath <# "#team dave> hello from dave [>>]" ] - -- profile update items on alice and bob (moderator+/relay, 2nd profile update signed) - alice #$> ("/_get chat #1 count=2", chat, [(0, "updated profile (signed)"), (0, "hello from dave")]) - bob #$> ("/_get chat #1 count=2", chat, [(0, "updated profile (signed)"), (0, "hello from dave")]) + -- no profile update items in main scope (dan has no support chat, item not created) + alice #$> ("/_get chat #1 count=2", chat, [(0, "hello from kate"), (0, "hello from dave")]) + bob #$> ("/_get chat #1 count=2", chat, [(0, "hello from kate"), (0, "hello from dave")]) -- no profile update items on cath and eve (subscriber-to-subscriber muted) cath #$> ("/_get chat #1 count=2", chat, [(1, "hello from kate"), (0, "hello from dave")]) eve #$> ("/_get chat #1 count=2", chat, [(0, "hello from kate"), (0, "hello from dave")]) -- dan doesn't see his own profile update dan #$> ("/_get chat #1 count=2", chat, [(0, "hello from kate"), (1, "hello from dave")]) + -- verify dan has no support chat (only kate has one) + alice ##> "/member support chats #team" + alice <## "members require attention: 0" + alice <## "kate (id 3): unread: 0, require attention: 0, mentions: 0" -- verify profiles are updated correctly forM_ [alice, bob] $ \cc -> cc `hasContactProfiles` ["alice", "bob", "kate", "dave", "eve"] cath `hasContactProfiles` ["alice", "bob", "kate", "dave"] @@ -9567,6 +9713,45 @@ testChannelCreateDeletedRelay ps = -- bob's agent reports AUTH error when the queue is gone — drain it. void $ getTermLine bob +testChannelSupportScope :: HasCallStack => TestParams -> IO () +testChannelSupportScope ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "relay" relayProfile $ \relay -> + withNewTestChat ps "cath" cathProfile $ \cath -> + withNewTestChat ps "dan" danProfile $ \dan -> do + (shortLink, fullLink) <- prepareChannel1Relay "team" alice relay + memberJoinChannel "team" [relay] [alice] shortLink fullLink cath + memberJoinChannel "team" [relay] [alice] shortLink fullLink dan + + threadDelay 1000000 + + alice ##> "/set support #team on" + alice <## "updated group preferences:" + alice <## "Chat with admins: on" + toggledSupport relay "alice" "team" "on" + concurrentlyN_ + [ toggledSupport cath "alice" "team" "on", + toggledSupport dan "alice" "team" "on" + ] + + -- owner sends to cath's support scope, dan doesn't receive + alice #> "#team (support: cath) hello" + relay <# "#team (support: cath) alice> hello" + cath <# "#team (support) alice> hello [>>]" + (dan "#team (support) hi" + relay <# "#team (support: cath) cath> hi" + alice <# "#team (support: cath) cath> hi [>>]" + (dan TestCC -> String -> String -> String -> IO () +toggledSupport c owner channel onOff = do + c <## (owner <> " updated group #" <> channel <> ": (signed)") + c <## "updated group preferences:" + c <## ("Chat with admins: " <> onOff) + testChannelMessageUpdate :: HasCallStack => TestParams -> IO () testChannelMessageUpdate ps = withNewTestChat ps "alice" aliceProfile $ \alice -> diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index ebc9056164..d42e833c39 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -290,7 +290,10 @@ groupFeatures :: [(Int, String)] groupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 0 groupFeaturesNoE2E :: [(Int, String)] -groupFeaturesNoE2E = map (\(a, _, _) -> a) $ ((1, "chat banner"), Nothing, Nothing) : groupFeatures_ 0 +groupFeaturesNoE2E = map (\(a, _, _) -> a) $ ((1, "chat banner"), Nothing, Nothing) : groupFeatures_ 0 False + +channelFeaturesNoE2E :: [(Int, String)] +channelFeaturesNoE2E = map (\(a, _, _) -> a) $ ((1, "chat banner"), Nothing, Nothing) : groupFeatures_ 0 True sndGroupFeatures :: [(Int, String)] sndGroupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 1 @@ -299,10 +302,10 @@ groupFeatureStrs :: [String] groupFeatureStrs = map (\(a, _, _) -> snd a) $ groupFeatures'' 0 groupFeatures'' :: Int -> [((Int, String), Maybe (Int, String), Maybe String)] -groupFeatures'' dir = ((1, "chat banner"), Nothing, Nothing) : ((dir, e2eeInfoNoPQStr), Nothing, Nothing) : groupFeatures_ dir +groupFeatures'' dir = ((1, "chat banner"), Nothing, Nothing) : ((dir, e2eeInfoNoPQStr), Nothing, Nothing) : groupFeatures_ dir False -groupFeatures_ :: Int -> [((Int, String), Maybe (Int, String), Maybe String)] -groupFeatures_ dir = +groupFeatures_ :: Int -> Bool -> [((Int, String), Maybe (Int, String), Maybe String)] +groupFeatures_ dir isChannel = [ ((dir, "Disappearing messages: off"), Nothing, Nothing), ((dir, "Direct messages: on"), Nothing, Nothing), ((dir, "Full deletion: off"), Nothing, Nothing), @@ -311,7 +314,8 @@ groupFeatures_ dir = ((dir, "Files and media: on"), Nothing, Nothing), ((dir, "SimpleX links: on"), Nothing, Nothing), ((dir, "Member reports: on"), Nothing, Nothing), - ((dir, "Recent history: on"), Nothing, Nothing) + ((dir, "Recent history: on"), Nothing, Nothing), + ((dir, "Chat with admins: " <> (if isChannel then "off" else "on")), Nothing, Nothing) ] businessGroupFeatures :: [(Int, String)] @@ -329,7 +333,8 @@ businessGroupFeatures'' dir = ((dir, "Files and media: on"), Nothing, Nothing), ((dir, "SimpleX links: on"), Nothing, Nothing), ((dir, "Member reports: off"), Nothing, Nothing), - ((dir, "Recent history: on"), Nothing, Nothing) + ((dir, "Recent history: on"), Nothing, Nothing), + ((dir, "Chat with admins: on"), Nothing, Nothing) ] itemId :: Int -> String diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 3ba789b988..01399f6bbb 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -101,7 +101,7 @@ testChatPreferences :: Maybe Preferences testChatPreferences = Just Preferences {voice = Just VoicePreference {allow = FAYes}, files = Nothing, fullDelete = Nothing, timedMessages = Nothing, calls = Nothing, reactions = Just ReactionsPreference {allow = FAYes}, sessions = Nothing, commands = Nothing} testGroupPreferences :: Maybe GroupPreferences -testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, sessions = Nothing, comments = Nothing, commands = Nothing} +testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, support = Nothing, sessions = Nothing, comments = Nothing, commands = Nothing} testProfile :: Profile testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences} diff --git a/website/.eleventy.js b/website/.eleventy.js index 1a98609f1a..cdbdc0520f 100644 --- a/website/.eleventy.js +++ b/website/.eleventy.js @@ -1,6 +1,7 @@ const markdownIt = require("markdown-it") const markdownItAnchor = require("markdown-it-anchor") const markdownItReplaceLink = require('markdown-it-replace-link') +const markdownItFootnote = require('markdown-it-footnote') const slugify = require("slugify") const uri = require('fast-uri') const i18n = require('eleventy-plugin-i18n') @@ -438,6 +439,38 @@ module.exports = function (ty) { strict: true, }) }).use(markdownItReplaceLink) + .use(markdownItFootnote) + + markdownLib.renderer.rules.footnote_anchor_name = function (tokens, idx, options, env) { + var token = tokens[idx] + var label = token.meta.label + if (label) return label + var n = Number(token.meta.id + 1).toString() + var prefix = typeof env.docId === 'string' ? '-' + env.docId + '-' : '' + return prefix + n + } + markdownLib.renderer.rules.footnote_caption = function (tokens, idx) { + var n = Number(tokens[idx].meta.id + 1).toString() + if (tokens[idx].meta.subId > 0) n += ':' + tokens[idx].meta.subId + return n + } + markdownLib.renderer.rules.footnote_ref = function (tokens, idx, options, env, slf) { + var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf) + var caption = slf.rules.footnote_caption(tokens, idx, options, env, slf) + var refid = id + if (tokens[idx].meta.subId > 0) refid += ':' + tokens[idx].meta.subId + return '' + caption + '' + } + markdownLib.renderer.rules.footnote_open = function (tokens, idx, options, env, slf) { + var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf) + if (tokens[idx].meta.subId > 0) id += ':' + tokens[idx].meta.subId + return '
  • ' + } + markdownLib.renderer.rules.footnote_anchor = function (tokens, idx, options, env, slf) { + var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf) + if (tokens[idx].meta.subId > 0) id += ':' + tokens[idx].meta.subId + return ' ↩︎' + } // replace the default markdown-it instance ty.setLibrary("md", markdownLib) diff --git a/website/README.md b/website/README.md index 5a5155a917..560b427136 100644 --- a/website/README.md +++ b/website/README.md @@ -2,8 +2,6 @@ ## License -SimpleX Chat website code is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](../LICENSE) file for details. +SimpleX Chat website code is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, associated branding materials, and graphic assets (illustrations, images, visual designs, etc.) are not covered by this license and are subject to the terms outlined in the [TRADEMARK](../docs/TRADEMARK.md) and [ASSETS_LICENSE](../assets/ASSETS_LICENSE.md) files respectively. -The SimpleX and SimpleX Chat name, logo, and associated branding materials are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) file. - -Graphic designs, artworks and layouts are not licensed for re-use. If you want to use them in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source. +If you want to use any graphic assets in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source. diff --git a/website/langs/de.json b/website/langs/de.json index cc79255259..c35cd8fba6 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -17,7 +17,7 @@ "simplex-explained-tab-2-p-1": "Für jede Verbindung nutzen Sie zwei separate Nachrichten-Warteschlangen, um die Nachrichten über verschiedene Server zu senden und zu empfangen.", "simplex-explained-tab-2-p-2": "Die Server leiten Nachrichten immer nur in eine Richtung weiter, ohne den vollständigen Verlauf der Nutzer-Unterhaltungen oder seiner Verbindungen zu kennen.", "simplex-explained-tab-3-p-1": "Die Server nutzen für jede Warteschlange separate, anonyme Anmeldeinformationen und wissen nicht, welchem Nutzer diese gehören.", - "simplex-explained-tab-3-p-2": "Durch die Verwendung von Tor-Zugangsservern können Nutzer ihre Metadaten-Privatsphäre weiter verbessern und Korellationen von IP-Adressen verhindern.", + "simplex-explained-tab-3-p-2": "Durch die Verwendung von Tor-Zugangsservern können Nutzer ihre Metadaten-Privatsphäre weiter verbessern und Korrelationen von IP-Adressen verhindern.", "smp-protocol": "SMP-Protokoll", "chat-bot-example": "Beispiel für einen Chatbot", "donate": "Spenden", @@ -68,7 +68,7 @@ "simplex-private-card-9-point-1": "Jede Nachrichten-Warteschlange leitet Nachrichten mit unterschiedlichen Sende- und Empfängeradressen jeweils nur in einer Richtung weiter.", "simplex-private-card-9-point-2": "Verglichen mit traditionellen Nachrichten-Brokern, werden mögliche Angriffs-Vektoren und vorhandene Metadaten reduziert.", "simplex-private-card-10-point-1": "SimpleX nutzt für jeden Nutzer-Kontakt oder jedes Gruppenmitglied eigene temporäre, anonyme und paarweise Adressen und Berechtigungsnachweise.", - "simplex-private-card-10-point-2": "SimpleX erlaubt es, Nachrichten ohne Nutzerprofil-Bezeichner zu versenden und bietet dabei bessere Metadaten-Privatsphäre an als andere Alternativen.", + "simplex-private-card-10-point-2": "SimpleX ermöglicht die Zustellung von Nachrichten ohne Nutzerprofil-Kennungen und bietet dabei bessere Metadaten-Privatsphäre als andere Alternativen.", "simplex-unique-4-title": "Sie besitzen das SimpleX-Netzwerk", "privacy-matters-1-title": "Werbung und Preisdiskriminierung", "privacy-matters-1-overlay-1-title": "Privatsphäre spart Ihnen Geld", @@ -197,13 +197,13 @@ "simplex-network-overlay-card-1-li-1": "P2P-Netzwerke vertrauen auf Varianten von DHT, um Nachrichten zu routen. DHT-Designs müssen zwischen Zustellungsgarantie und Latenz ausgleichen. Verglichen mit P2P bietet SimpleX sowohl eine bessere Zustellungsgarantie als auch eine niedrigere Latenz, weil eine Nachricht redundant und parallel über mehrere Server gesendet werden kann, wobei die durch den Empfänger ausgewählten Server genutzt werden. In P2P-Netzwerken werden Nachrichten sequentiell über O(log N)-Knoten gesendet, wobei die Knoten durch einen Algorithmus ausgewählt werden.", "simplex-unique-overlay-card-3-p-4": "Zwischen dem gesendeten und dem empfangenen Serververkehr gibt es keine gemeinsamen Kennungen oder Chiffriertexte — sodass ein Beobachter nicht ohne weiteres feststellen kann, wer mit wem kommuniziert, selbst wenn TLS kompromittiert wurde.", "simplex-unique-overlay-card-4-p-3": "Falls Sie Interesse daran haben, aktiv bei der Entwicklung des SimpleX-Netzwerks mitzuhelfen, z.B. einen Chatbot für SimpleX App-Nutzer zu entwickeln oder die Integration der SimpleX Chat-Bibliothek in mobile Apps voranzutreiben, kontaktieren Sie uns bitte für eine weitere Beratung und Unterstützung.", - "privacy-matters-overlay-card-1-p-4": "Das SimpleX-Netzwerk schützt die Privatsphäre Ihrer Verbindungen besser als jede Alternative und verhindert vollständig, dass Ihr sozialer Graph für Unternehmen oder Organisationen verfügbar wird. Selbst wenn Anwender die in der SimpleX Chat-App vorkonfigurierten Server verwenden, kennen die Server-Betreiber die Anzahl der Benutzer oder deren Verbindungen nicht.", + "privacy-matters-overlay-card-1-p-4": "Das SimpleX-Netzwerk schützt die Privatsphäre Ihrer Verbindungen besser als jede Alternative und verhindert vollständig, dass Ihr sozialer Graph für Unternehmen oder Organisationen einsehbar wird. Selbst wenn Anwender die in der SimpleX Chat-App vorkonfigurierten Server verwenden, kennen die Server-Betreiber die Anzahl der Benutzer oder deren Verbindungen nicht.", "contact-hero-header": "Sie haben eine Adresse zur Verbindung mit SimpleX Chat erhalten", "invitation-hero-header": "Sie haben einen Einmal-Link zur Verbindung mit SimpleX Chat erhalten", "privacy-matters-overlay-card-3-p-3": "Selbst in demokratischen Ländern werden normale Menschen, auch unter Nutzung ihrer „anonymen“ Benutzerkennungen, für das, was sie online teilen, verhaftet.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat speichert alle Benutzerdaten ausschließlich auf den Endgeräten in einem portablen und verschlüsselten Datenbankformat, welches exportiert und auf jedes unterstützte Gerät übertragen werden kann.", "simplex-unique-overlay-card-2-p-2": "Auch wenn die optionale Benutzeradresse zum Versenden von Spam-Kontaktanfragen verwendet werden kann, können Sie sie ändern oder ganz löschen, ohne dass Ihre Verbindungen verloren gehen.", - "simplex-unique-overlay-card-4-p-2": "Das SimpleX-Netzwerk verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an. Dies ermöglicht die Erstellung von Diensten, mit denen Nutzer über SimpleX Chat-Apps interagieren können — wir sind gespannt, welche SimpleX-Dienste Sie entwickeln werden.", + "simplex-unique-overlay-card-4-p-2": "Das SimpleX-Netzwerk verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an. Dies ermöglicht die Erstellung von Diensten, mit denen Nutzer über SimpleX Chat-Apps interagieren können — wir freuen uns sehr darauf zu sehen, welche SimpleX-Dienste Sie entwickeln werden.", "simplex-unique-card-4-p-2": "Sie können SimpleX mit eigenen Servern oder mit den von uns zur Verfügung gestellten Servern verwenden — und sich trotzdem mit jedem Benutzer verbinden.", "why-simplex-is-unique": "Warum ist SimpleX einmalig", "contact-hero-p-1": "Die öffentlichen Schlüssel und die Adresse der Nachrichtenwarteschlange in diesem Link werden NICHT über das Netzwerk gesendet, wenn Sie diese Seite aufrufen — sie sind in dem Hash-Fragment der Link-URL enthalten.", @@ -275,7 +275,7 @@ "index-publications-optout-title": "Podcast-Interview von OptOut", "worlds-most-secure-messaging": "Das sicherste Messaging-System der Welt", "index-messaging-p1": "SimpleX-Messaging verfügt über modernste Ende-zu-Ende-Verschlüsselung.", - "index-messaging-p2": "Zu Ihrer Sicherheit und zum Schutz Ihrer Privatsphäre können Server weder Ihre Nachrichten sehen, noch mit wem Sie kommunizieren.", + "index-messaging-p2": "Zu Ihrer Sicherheit und zum Schutz Ihrer Privatsphäre können Server weder Ihre Nachrichten sehen, noch mit wem Sie kommunizieren.", "index-messaging-cta": "Lernen Sie mehr über SimpleX-Messaging", "index-nextweb-h2": "Sie besitzen die
    Zukunft des Webs", "index-nextweb-p1": "SimpleX wurde aus der Überzeugung heraus entwickelt, dass Sie Eigentümer Ihrer Profile, Kontakte und Communitys bleiben müssen.", diff --git a/website/langs/es.json b/website/langs/es.json index 421cdbe9bd..1826885863 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -88,7 +88,7 @@ "hero-overlay-card-2-p-2": "A continuación podrían correlacionar esta información con las redes sociales públicas existentes y averiguar las identidades reales de los usuarios.", "hero-overlay-card-2-p-3": "Incluso con las aplicaciones más privadas que usan los servicios de Tor v3, si hablas con dos contactos a través de un mismo perfil se puede probar que estos están conectados con la misma persona.", "privacy-matters-2-overlay-1-linkText": "La privacidad te da poder", - "simplex-private-card-10-point-2": "Esto permite entregar mensajes sin identificadores del perfil de usuario y proporcionar mayor privacidad de metadatos que las alternativas.", + "simplex-private-card-10-point-2": "Permite la entrega de mensajes sin identificadores del perfil de usuario y proporciona mayor privacidad de metadatos que las alternativas.", "privacy-matters-3-overlay-1-title": "La privacidad protege tu libertad", "simplex-unique-1-overlay-1-title": "Privacidad total de tu identidad, perfil, contactos y metadatos", "privacy-matters-2-title": "Manipulación de elecciones", @@ -115,7 +115,7 @@ "simplex-unique-overlay-card-3-p-2": "Los mensajes cifrados de extremo a extremo (E2E) se mantienen temporalmente en los servidores SimpleX hasta que se entregan al destinatario, y después se borran definitivamente.", "simplex-network-overlay-card-1-li-1": "Para enrutar mensajes, las redes P2P se basan en alguna variante de DHT. Los diseños DHT tienen que equilibrar la garantía de entrega y latencia. SimpleX ofrece mayor garantía de entrega y menor latencia que P2P ya que el mensaje puede transmitirse en paralelo y de forma redundante a través de varios servidores elegidos por el destinatario. En las redes P2P el mensaje se transmite secuencialmente por O(log N) nodos, usando nodos elegidos por el algoritmo.", "simplex-network-overlay-card-1-li-5": "Todas las redes P2P conocidas pueden ser vulnerables al ataque Sybil porque cada nodo es susceptible de ser descubierto y la red funciona como una unidad. Las medidas de mitigación conocidas requieren un componente centralizado o bien costosas pruebas de trabajo. La red SimpleX no tiene la capacidad para descubrir los servidores, está fragmentada y funciona como múltiples subredes aisladas haciendo imposibles los ataques a toda la red.", - "simplex-network-overlay-card-1-li-6": "Las redes P2P pueden ser vulnerables al ataque DRDoS, en el que los clientes retransmiten y amplifican el tráfico provocando el bloqueo del servicio en la red. Los clientes SimpleX sólo transmiten el tráfico desde una conexión conocida y no pueden ser usados por un atacante para amplificar el tráfico en toda la red.", + "simplex-network-overlay-card-1-li-6": "Las redes P2P pueden ser vulnerables al ataque DRDoS, en el que los clientes retransmiten y amplifican el tráfico provocando el bloqueo del servicio en la red. Los clientes SimpleX sólo transmiten el tráfico desde conexiones conocidas y no pueden ser usados por un atacante para amplificar el tráfico en toda la red.", "privacy-matters-overlay-card-1-p-1": "Las grandes empresas usan la información relativa a tus contactos para calcular tus ingresos, venderte productos que realmente no necesitas y para determinar sus precios.", "privacy-matters-overlay-card-1-p-2": "Los minoristas en línea saben que las personas con menos ingresos son más propensas a hacer compras urgentes, por lo que pueden cobrar precios más altos o eliminar descuentos.", "privacy-matters-overlay-card-1-p-3": "Algunas compañías financieras y de seguros usan las gráficas sociales para determinar primas y tipos de interés. Eso genera a menudo un mayor desembolso a personas con menores ingresos, conocido como \"prima de pobreza\".", @@ -147,7 +147,7 @@ "contact-hero-p-3": "Usa los siguientes enlaces para descargar la aplicación.", "install-simplex-app": "Instalar SimpleX Chat", "connect-in-app": "Conectar en la aplicación", - "open-simplex-app": "Abrir la aplicación Simplex", + "open-simplex-app": "Abrir la app SimpleX", "tap-the-connect-button-in-the-app": "Pulsa el botón \"conectar\" en la aplicación", "scan-the-qr-code-with-the-simplex-chat-app": "Escanear el código QR con la aplicación SimpleX Chat", "installing-simplex-chat-to-terminal": "Instalación de chat SimpleX en el terminal", @@ -184,7 +184,7 @@ "comparison-section-list-point-6": "A pesar de que las redes P2P son distribuidas, no son federadas. Funcionan como una única red", "comparison-section-list-point-7": "Las redes P2P, o bien disponen de una autoridad central, o bien la red completa podría verse comprometida", "see-here": "ver aquí", - "simplex-unique-overlay-card-4-p-2": "La red SimpleX usa un protocolo abierto y proporciona el SDK para crear chatbots, permitiendo implementar servicios con los que los usuarios interactúen con las aplicaciones SimpleX Chat — esperamos con interés ver los servicios SimpleX que creará usted.", + "simplex-unique-overlay-card-4-p-2": "La red SimpleX usa un protocolo abierto y proporciona el SDK para crear chatbots, permitiendo implementar servicios con los que los usuarios interactúen con las aplicaciones SimpleX Chat — esperamos ver con interés los servicios SimpleX que creará usted.", "simplex-unique-overlay-card-4-p-3": "Si estás considerando desarrollar para la red SimpleX, por ejemplo un chatbot para los usuarios de SimpleX o la integración de las librerías SimpleX Chat en tus aplicaciones móviles, por favor ponte en contacto para consejos y soporte.", "simplex-unique-card-1-p-2": "A diferencia de cualquier otra red de mensajería existente, SimpleX no tiene identificadores asignados a los usuarios, ni siquiera números aleatorios.", "simplex-unique-card-2-p-1": "Al no tener identificadores o dirección permanente en la red SimpleX, nadie puede ponerse en contacto contigo salvo que compartas una dirección de usuario de un solo uso o una dirección temporal en forma de enlace o código QR.", @@ -336,7 +336,7 @@ "file-drop-text": "Arrastra y suelta el archivo aquí", "file-drop-hint": "o", "file-choose": "Seleccionar archivo", - "file-max-size": "Max 100 MB - La app SimpleX Chat soporta archivos hasta 1GB", + "file-max-size": "Max 100 MB - La app SimpleX Chat soporta archivos hasta 1GB", "file-encrypting": "Cifrando…", "file-uploading": "Subiendo…", "file-cancel": "Cancelar", @@ -347,7 +347,7 @@ "file-expiry": "Normalmente los archivos están disponibles durante 48 horas.", "file-sec-1": "Tu archivo se ha cifrado en el navegador, los routers de datos nunca ven el contenido, el nombre o el tamaño.", "file-sec-2": "La clave de cifrado está en el fragmento hash del enlace, nunca se envía a ningún servidor.", - "file-sec-3": "Para mejor seguridad, usa la app SimpleX Chat.", + "file-sec-3": "Para mejor seguridad, usa la app SimpleX Chat.", "file-retry": "Reintentar", "file-downloading": "Descargando…", "file-decrypting": "Descifrando…", diff --git a/website/langs/hu.json b/website/langs/hu.json index da5de1128d..1d0d51fc03 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -11,7 +11,7 @@ "simplex-explained-tab-1-text": "1. Felhasználói élmény", "simplex-explained-tab-2-text": "2. Hogyan működik", "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", - "simplex-explained-tab-1-p-1": "Csoportokat hozhat létre, valamint kétirányú beszélgetéseket folytathat a partnereivel, ugyanúgy mint bármely más üzenetváltó-alkalmazásban.", + "simplex-explained-tab-1-p-1": "Csoportokat hozhat létre, valamint kétirányú beszélgetéseket folytathat a partnereivel, ugyanúgy mint bármely más üzenetváltó alkalmazásban.", "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú várólistával és felhasználói profilazonosítók nélkül?", "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési várólistát használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyetlen irányba továbbítják az üzeneteket, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", @@ -25,7 +25,7 @@ "terminal-cli": "Terminál CLI", "terms-and-privacy-policy": "Adatvédelmi irányelvek", "hero-header": "Újraértelmezett adatvédelem", - "hero-subheader": "Az első üzenetváltó-alkalmazás
    felhasználói azonosítók nélkül", + "hero-subheader": "Az első üzenetváltó alkalmazás
    felhasználói azonosítók nélkül", "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
    A SimpleX azonban nem, még véletlenszerű számokkal sem.
    Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", @@ -66,7 +66,7 @@ "simplex-private-card-7-point-2": "Ha bármilyen üzenetet hozzáadnak, eltávolítanak vagy módosítanak, a címzett értesítést kap róla.", "simplex-private-card-8-point-1": "A SimpleX kiszolgálók alacsony késleltetésű keverési csomópontokként működnek — a bejövő és kimenő üzenetek sorrendje eltérő.", "simplex-private-card-9-point-1": "Minden várólista az üzenetekhez egy irányba továbbítja az üzeneteket, a különböző küldési és fogadási címeken.", - "simplex-private-card-9-point-2": "Kevesebb támadási felülettel rendelkezik, mint a hagyományos üzenetváltó-alkalmazások, és kevesebb metaadatot tesz elérhetővé.", + "simplex-private-card-9-point-2": "Kevesebb támadási felülettel rendelkezik, mint a hagyományos üzenetváltó alkalmazások, és kevesebb metaadatot tesz elérhetővé.", "simplex-private-card-10-point-1": "A SimpleX ideiglenes, névtelen, páros címeket és hitelesítő adatokat használ minden egyes felhasználói kapcsolathoz vagy csoporttaghoz.", "simplex-private-card-10-point-2": "Lehetővé teszi az üzenetek felhasználói profilazonosítók nélküli kézbesítését, ami az alternatíváknál jobb metaadat-védelmet biztosít.", "privacy-matters-1-overlay-1-title": "Az adatvédelemmel pénzt spórol meg", @@ -99,19 +99,19 @@ "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenessel sem, és csak az üzenetekhez használ ideiglenes, páros azonosítókat, ami jobb névtelenséget és metaadat-védelmet biztosít.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-támadás problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", - "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX kliensek csak az ismert kapcsolatból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", + "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX kliensek csak az ismert kapcsolatokból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (például: tracker), vagy egy drága tanúsítvány. A SimpleX hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", "privacy-matters-overlay-card-1-p-1": "Sok nagyvállalat arra használja fel a felhasználóival kapcsolatban álló személyek adatait, hogy megbecsülje a jövedelmi helyzetüket és olyan termékeket kínáljon fel, amelyekre valójában nincs is szükségük, valamint hogy meghatározza az árakat.", "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudják, hogy az alacsonyabb jövedelműek nagyobb valószínűséggel vásárolnak azonnal, ezért magasabb árakat számíthatnak fel, vagy eltörölhetik a kedvezményeket.", "privacy-matters-overlay-card-1-p-3": "Egyes pénzügyi és biztosítótársaságok szociális grafikonokat használnak a kamatlábak és a díjak meghatározásához. Ez gyakran arra készteti az alacsonyabb jövedelmű embereket, hogy többet fizessenek — ez az úgynevezett „szegénységi prémium”.", "privacy-matters-overlay-card-1-p-4": "A SimpleX hálózat minden alternatívánál jobban védi a kapcsolatai adatait, teljes mértékben megakadályozva, hogy az ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által előre beállított kiszolgálókat is használják, sem az alkalmazások, sem a kiszolgálók üzemeltetői nem ismerik, sem felhasználók számát, sem a kapcsolataikat.", "privacy-matters-overlay-card-2-p-1": "Nem is olyan régen megfigyelhettük, hogy a nagy választásokat manipulálta egy neves tanácsadó cég, amely az ismeretségi-háló segítségével eltorzította a valós világról alkotott képünket, és manipulálta a szavazatainkat.", - "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha privát kommunikációs hálózatot használ, amely nem fér hozzá az ismeretségi hálójához.", + "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha egy privát kommunikációs hálózatot használ, amely nem fér hozzá az ismeretségi hálójához.", "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan hálózat, amely eleve nem rendelkezik felhasználói azonosítókkal, így jobban védi az ismeretségi-hálóját, mint bármely ismert alternatíva.", "privacy-matters-overlay-card-3-p-1": "Mindenkinek törődnie kell a magánélet és a kommunikáció biztonságával — az ártalmatlan beszélgetések veszélybe sodorhatják, még akkor is, ha nincs semmi rejtegetnivalója.", "privacy-matters-overlay-card-3-p-2": "Az egyik legmegdöbbentőbb a Mohamedou Ould Salahi memoárjában leírt és az „A mauritániai” c. filmben bemutatott történet. Őt bírósági tárgyalás nélkül a guantánamói táborba zárták, és ott kínozták 15 éven át, miután egy afganisztáni rokonát telefonon felhívta, akit azzal gyanúsítottak a hatóságok, hogy köze van a 9/11-es merényletekhez, holott Salahi az előző 10 évben Németországban élt.", "privacy-matters-overlay-card-3-p-3": "Átlagos embereket letartóztatnak azért, amit online megosztanak, még „névtelen” fiókjaikon keresztül is, még demokratikus országokban is.", - "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó-alkalmazást használnunk, mindannyiunknak olyan üzenetváltó-alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", + "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó alkalmazást használnunk, mindannyiunknak olyan üzenetváltó alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő hálózatoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — a SimpleX kiszolgálók üzemeltetői nem tudják, hogy hányan használják a kiszolgálóikat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX egyirányú várólistákat használ az üzenetekhez, páronkénti, névtelen címekkel, külön a fogadott és külön az elküldött üzenetekhez, általában különböző kiszolgálókon keresztül.", "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás védi partnerei adatait, elrejtve azt a SimpleX hálózat kiszolgálói és a külső megfigyelők elől. Az IP-címe elrejtésének érdekében a Tor hálózaton keresztül is kapcsolódhat a SimpleX kiszolgálókhoz.", @@ -150,7 +150,7 @@ "scan-qr-code-from-mobile-app": "QR-kód beolvasása mobilalkalmazásból", "to-make-a-connection": "A kapcsolat létrehozásához:", "install-simplex-app": "Telepítse a SimpleX alkalmazást", - "open-simplex-app": "Simplex alkalmazás megnyitása", + "open-simplex-app": "SimpleX alkalmazás megnyitása", "tap-the-connect-button-in-the-app": "Koppintson a „kapcsolódás” gombra az alkalmazásban", "scan-the-qr-code-with-the-simplex-chat-app": "Olvassa be a QR-kódot a SimpleX Chat alkalmazással", "scan-the-qr-code-with-the-simplex-chat-app-description": "A hivatkozásban szereplő nyilvános kulcsok és az üzenetek várólistájának címe NEM lesz elküldve a hálózaton keresztül, amikor megtekinti ezt az oldalt —
    azokat, a hivatkozás webcímének kivonattöredéke tartalmazza.", @@ -165,7 +165,7 @@ "copy-the-command-below-text": "másolja be az alábbi parancsot, és használja a csevegésben:", "privacy-matters-section-header": "Miért számít az adatvédelem", "privacy-matters-section-subheader": "A metaadatok — például, hogy kivel beszélget — védelmének megőrzése biztonságot nyújt a következők ellen:", - "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó-alkalmazás amit használ nem fér hozzá az adataihoz!", + "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó alkalmazás amit használ nem fér hozzá az adataihoz!", "simplex-private-section-header": "Mitől lesz a SimpleX privát", "simplex-network-section-header": "SimpleX hálózat", "simplex-network-section-desc": "A SimpleX Chat a P2P- és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", @@ -220,7 +220,7 @@ "jobs": "Csatlakozzon a csapathoz", "please-enable-javascript": "Engedélyezze a JavaScriptet a QR-kód megjelenítéséhez.", "please-use-link-in-mobile-app": "Használja a hivatkozást a SimpleX Chat alkalmazásban", - "contact-hero-header": "Meghívót kapott a SimpleX Chaten való beszélgetéshez", + "contact-hero-header": "Ez egy meghívó a SimpleX Chaten való beszélgetéshez", "invitation-hero-header": "Kapott egy egyszer használható meghívót a SimpleX Chaten való beszélgetéshez", "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független — a szabványos webes protokollokon, például WebSocketsen keresztül is működik.", "simplex-private-card-4-point-2": "A SimpleX, Tor hálózaton keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", @@ -316,7 +316,7 @@ "navbar-token": "Token", "navbar-old-site": "Régi oldal", "docs-dropdown-15": "Összeállítások ellenőrzése és reprodukálása", - "why-p2": "Senki sem követte nyomon a beszélgetéseit. Senki sem készített térképet arról, hogy merre járt. A magánélet nem csak egy funkció volt, hanem egy életmód.", + "why-p2": "Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk.", "why-p3": "Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot — nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött — telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód.", "why-p4": "De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá.", "why-p5": "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide — Ön itt szuverén.", @@ -325,7 +325,7 @@ "why-p8": "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe.", "why-tagline": "Legyen szabad a saját hálózatában.", "why-footer-link": "Miért készítjük", - "why-p1": "Ön fiók nélkül született.", + "why-p1": "Fiók nélkül születtünk.", "file": "Fájl", "file-desc": "Fájlok biztonságos küldése végpontok közötti titkosítással – felhasználói fiókok és nyomon követés nélkül.", "file-noscript": "A fájlátvitelhez JavaScript szükséges.", @@ -348,7 +348,7 @@ "file-expiry": "A fájlok általában 48 óráig érhetők el.", "file-sec-1": "A fájl a böngészőben lett titkosítva – az útválasztók soha nem „látják” a fájl tartalmát, nevét és méretét.", "file-sec-2": "A titkosítási kulcs a hivatkozás kivonattöredékében található – soha nem kerül elküldésre semmilyen kiszolgálóra.", - "file-sec-3": "A nagyobb biztonság érdekében használja a SimpleX Chat alkalmazást.", + "file-sec-3": "A nagyobb biztonság érdekében, használja a SimpleX Chat alkalmazást.", "file-retry": "Újra", "file-downloading": "Letöltés…", "file-decrypting": "Visszafejtés…", diff --git a/website/langs/it.json b/website/langs/it.json index 0a6870d75d..326afb82d4 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -62,8 +62,8 @@ "simplex-network-overlay-card-1-li-2": "Il design di SimpleX, a differenza della maggior parte delle reti P2P, non ha identificatori utente globali di alcun tipo, nemmeno temporanei, e usa solo identificatori temporanei a coppie, garantendo una maggiore protezione dell'anonimato e dei metadati.", "simplex-network-overlay-card-1-li-4": "Le implementazioni P2P possono essere bloccate da alcuni fornitori di internet (come BitTorrent). SimpleX è indipendente dal trasporto — può funzionare su protocolli web standard, es. WebSocket.", "hero-overlay-card-2-p-1": "Quando gli utenti hanno identità permanenti, anche se si tratta solo di un numero casuale, come un Session ID, c'è il rischio che il fornitore o un malintenzionato possano osservare come gli utenti sono connessi e quanti messaggi inviano.", - "simplex-network-overlay-card-1-li-6": "Le reti P2P possono essere vulnerabili all'attacco DRDoS, quando i client possono ritrasmettere e amplificare il traffico, con conseguente \"denial of service\" a livello di rete. I client SimpleX si limitano a inoltrare il traffico da una connessione nota e non possono essere usati da un aggressore per amplificare il traffico nell'intera rete.", - "privacy-matters-overlay-card-1-p-4": "La rete di SimpleX protegge la privacy delle tue connessioni meglio di qualsiasi alternativa, impedendo completamente che il tuo grafico sociale sia disponibile a qualsiasi azienda o organizzazione. Anche quando le persone usano i server preconfigurati in SimpleX Chat, gli operatori dei server non conoscono il numero di utenti o le loro connessioni.", + "simplex-network-overlay-card-1-li-6": "Le reti P2P possono essere vulnerabili all'attacco DRDoS, quando i client possono ritrasmettere e amplificare il traffico, con conseguente \"denial of service\" a livello di rete. I client SimpleX si limitano a inoltrare il traffico da connessioni note e non possono essere usati da un aggressore per amplificare il traffico nell'intera rete.", + "privacy-matters-overlay-card-1-p-4": "La rete di SimpleX protegge la privacy delle tue connessioni meglio di qualsiasi alternativa, impedendo completamente che il tuo grafico sociale diventi disponibile a qualsiasi azienda o organizzazione. Anche quando le persone usano i server preconfigurati in SimpleX Chat, gli operatori dei server non conoscono il numero di utenti o le loro connessioni.", "privacy-matters-overlay-card-2-p-3": "SimpleX è la prima rete che non ha alcun identificatore utente per design, proteggendo così il tuo grafico delle connessioni meglio di qualsiasi alternativa conosciuta.", "privacy-matters-overlay-card-3-p-1": "Tutti dovrebbero preoccuparsi della privacy e della sicurezza delle proprie comunicazioni — conversazioni innocue possono metterti in pericolo, anche se non hai nulla da nascondere.", "privacy-matters-overlay-card-3-p-3": "Le persone comuni vengono arrestate per ciò che condividono online, anche tramite i loro account \"anonimi\", anche nei Paesi democratici.", @@ -139,7 +139,7 @@ "simplex-private-card-6-point-1": "Molte reti di comunicazione sono vulnerabili agli attacchi MITM da parte di server o fornitori di rete.", "simplex-private-card-7-point-1": "Per garantire l'integrità, i messaggi sono numerati in sequenza e includono l'hash del messaggio precedente.", "simplex-private-card-9-point-2": "Riduce i vettori di attacco, rispetto ai broker di messaggi tradizionali, e i metadati disponibili.", - "simplex-private-card-10-point-2": "Ciò consente di recapitare messaggi senza identificatori del profilo utente, garantendo una migliore privacy dei metadati rispetto alle alternative.", + "simplex-private-card-10-point-2": "Ciò consente ai messaggi di venire recapitati senza identificatori del profilo utente, garantendo una migliore privacy dei metadati rispetto alle alternative.", "privacy-matters-1-title": "Pubblicità e discriminazione dei prezzi", "privacy-matters-2-overlay-1-title": "La privacy ti dà potere", "simplex-private-card-9-point-1": "Ogni coda di messaggi passa i messaggi in una direzione, con i diversi indirizzi di invio e ricezione.", @@ -181,7 +181,7 @@ "privacy-matters-overlay-card-1-p-2": "I rivenditori online sanno che le persone con redditi più bassi sono più propense a fare acquisti urgenti, quindi possono applicare prezzi più alti o rimuovere sconti.", "privacy-matters-overlay-card-1-p-3": "Alcune società finanziarie e assicurative usano grafici sociali per determinare i tassi di interesse e i premi. Spesso ciò fa pagare di più le persone con redditi più bassi — è noto come \"premio di povertà\".", "privacy-matters-overlay-card-2-p-1": "Non molto tempo fa abbiamo assistito alla manipolazione delle principali elezioni da una rispettabile società di consulenza che ha usato i nostri grafici sociali per distorcere la nostra visione del mondo reale e manipolare i nostri voti.", - "privacy-matters-overlay-card-2-p-2": "Per essere obiettivi e prendere decisioni indipendenti devi avere il controllo del tuo spazio informativo. È possibile solo se utilizzi una rete di comunicazione privata che non ha accesso al tuo grafico sociale.", + "privacy-matters-overlay-card-2-p-2": "Per essere obiettivi e prendere decisioni indipendenti devi avere il controllo del tuo spazio informativo. È possibile solo se usi una rete di comunicazione privata che non ha accesso al tuo grafico sociale.", "privacy-matters-overlay-card-3-p-2": "Una delle storie più scioccanti è l'esperienza di Mohamedou Ould Salahi descritta nel suo libro di memorie e mostrata nel film The Mauritanian. È stato rinchiuso nel campo di Guantánamo, senza processo, e lì è stato torturato per 15 anni dopo una telefonata a un suo parente in Afghanistan, sospettato di essere coinvolto negli attacchi dell'11/9, nonostante avesse vissuto in Germania per i precedenti 10 anni.", "join-us-on-GitHub": "Unisciti a noi su GitHub", "simplex-chat-for-the-terminal": "SimpleX Chat per il terminale", diff --git a/website/langs/ja.json b/website/langs/ja.json index aff6d62c1a..fcec6944e3 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -46,7 +46,7 @@ "simplex-explained-tab-3-text": "3. サーバーが認識するもの", "smp-protocol": "SMPプロトコル", "simplex-explained-tab-2-p-1": "接続ごとに 2 つの個別のメッセージング キューを使用して、異なるサーバー経由でメッセージを送受信します。", - "simplex-explained-tab-2-p-2": "サーバーは、ユーザーの会話や接続の全体像を把握することなく、メッセージを一方向に渡すだけです。", + "simplex-explained-tab-2-p-2": "サーバーは、ユーザーの会話や接続の全体を把握することなく、メッセージを一方向に送信するだけです。", "simplex-explained-tab-3-p-1": "サーバーはキューごとに個別の匿名認証情報を持っており、どのユーザーに属しているかはわかりません。", "simplex-explained-tab-3-p-2": "ユーザーは、Tor を使用してサーバーにアクセスし、IP アドレスによる相関を防ぐことで、メタデータのプライバシーをさらに向上させることができます。", "chat-protocol": "チャットプロトコル", @@ -63,7 +63,7 @@ "feature-7-title": "ポータブルな暗号化データベース — プロファイルを別のデバイスに移動する", "no-federated": "いいえ - 連合型", "simplex-unique-overlay-card-3-p-3": "電子メール、XMPP、Matrixなどの連携ネットワークサーバーとは異なり、SimpleXサーバーはユーザーアカウントを保存せず、メッセージの中継のみを行い、双方のプライバシーを保護します。", - "privacy-matters-overlay-card-3-p-2": "最も衝撃的な話の 1 つは、Mohamedou Ould Salahiの経験であり、彼の回顧録に記述され、『モーリタニア映画』で紹介されました。 彼は裁判も受けずにグアンタナモ収容所に入れられ、それまで10年間ドイツに住んでいたにも関わらず、9/11攻撃への関与の疑いでアフガニスタンの親戚に電話をかけた後、そこで15年間拷問を受けました。", + "privacy-matters-overlay-card-3-p-2": "最も衝撃的な話の 1 つは、Mohamedou Ould Salahiの経験であり、彼の回顧録に記述され、『モーリタニア映画』で紹介されています。彼はアフガニスタンの親戚に電話をかけた後、アメリカ同時多発テロへの関与の疑いで拘束されました。 彼は裁判も受けずにグアンタナモ収容所に入れられ、そこで15年間拷問を受けました。", "signing-key-fingerprint": "署名キーのフィンガープリント (SHA-256)", "simplex-network-2-desc": "SimpleX リレー サーバーは、ユーザー プロファイル、連絡先、配信されたメッセージを保存せず、相互に接続せず、サーバー ディレクトリもありません。", "docs-dropdown-5": "ホストXFTPサーバー", @@ -99,7 +99,7 @@ "privacy-matters-section-subheader": "メタデータのプライバシーを保護する — 話す相手 — 以下のことからあなたを守ります:", "if-you-already-installed": "すでにインストールしている場合", "join": "参加", - "privacy-matters-section-header": "プライバシーが重要である理由", + "privacy-matters-section-header": "なぜプライバシーが重要なのか", "on-this-page": "このページでは", "privacy-matters-overlay-card-1-p-2": "オンライン小売業者は、収入が低い人ほど急ぎの買い物をする可能性が高いことを知っているため、より高い価格を請求したり、割引を廃止したりすることがあります。", "simplex-unique-3-overlay-1-title": "データの所有権、管理、セキュリティ", @@ -180,7 +180,7 @@ "simplex-network-3-header": "SimpleX ネットワーク", "comparison-section-list-point-4": "オペレーターのサーバーが侵害された場合。 Signal およびその他の一部のアプリでセキュリティ コードを検証して緩和する", "simplex-private-card-2-point-1": "TLSが侵害された場合、受信したサーバー・トラフィックと送信したサーバー・トラフィックの相関を防ぐため、受信者に配信するサーバー暗号化レイヤーを追加します。", - "f-droid-page-simplex-chat-repo-section-text": "F-Droid クライアントに追加するには、QR コードをスキャンするか、次の URL を使用します:", + "f-droid-page-simplex-chat-repo-section-text": "F-Droid クライアントに追加するには、QR コードをスキャンするか、次の URL を使用してください:", "join-the-REDDIT-community": "REDDITコミュニティに参加する", "simplex-private-card-10-point-2": "ユーザー プロファイル識別子なしでメッセージを配信できるため、他の方法よりも優れたメタデータ プライバシーが提供されます。", "privacy-matters-2-title": "選挙操作", @@ -189,15 +189,15 @@ "feature-6-title": "E2E暗号化された
    音声通話とビデオ通話", "simplex-network-overlay-card-1-li-2": "SimpleX 設計は、ほとんどの P2P ネットワークとは異なり、一時的であってもいかなる種類のグローバル ユーザー識別子も持たず、一時的なペアごとの識別子のみを使用するため、より優れた匿名性とメタデータ保護が提供されます。", "simplex-unique-4-title": "SimpleX ネットワークを所有", - "privacy-matters-overlay-card-3-p-3": "一般の人が、たとえ「匿名」アカウント経由であっても、オンラインで共有した内容で逮捕されます。たとえ民主主義国家であったとしても。", + "privacy-matters-overlay-card-3-p-3": "一般の人が、たとえ「匿名」アカウント経由であっても、オンラインで共有した内容で逮捕されます。それは、たとえ民主主義の国であったとしても同じです。", "simplex-unique-overlay-card-3-p-2": "エンドツーエンドで暗号化されたメッセージは、SimpleXのリレーサーバーで受信するまで一時的に保持され、その後永久に削除されます。", "simplex-private-card-7-point-1": "整合性を保証するために、メッセージには連続した番号が付けられ、前のメッセージのハッシュが含まれます。", "contact-hero-p-2": "SimpleX Chat をまだダウンロードしていませんか?", - "why-simplex-is-unique": "なぜSimpleXなのか唯一", + "why-simplex-is-unique": "なぜSimpleXが唯一無二なのか", "simplex-network-section-header": "SimpleX ネットワーク", "simplex-private-10-title": "一時的な匿名のペア識別子", "privacy-matters-1-overlay-1-linkText": "プライバシーの保護はコストを削減します", - "tap-the-connect-button-in-the-app": "アプリの 「接続」 ボタンをタップします", + "tap-the-connect-button-in-the-app": "アプリの 「接続」 ボタンをタップしてください", "comparison-section-list-point-4a": "SimpleX リレーは e2e 暗号化を侵害できません。 セキュリティ コードを検証して帯域外チャネルへの攻撃を軽減します", "simplex-network-1-overlay-linktext": "P2Pネットワークの問題点", "no-private": "いいえ - プライベート", @@ -208,7 +208,7 @@ "hero-overlay-2-title": "ユーザー ID がプライバシーに悪影響を与えるのはなぜですか?", "docs-dropdown-4": "ホストSMPサーバー", "feature-4-title": "E2E暗号化された音声メッセージ", - "privacy-matters-overlay-card-2-p-1": "つい最近まで、私たちは主要な選挙が 評判の高いコンサルティング会社によって操作されているのを観察しました。 ソーシャルグラフは私たちの現実世界の見方を歪め、私たちの投票を操作します。", + "privacy-matters-overlay-card-2-p-1": "つい最近まで、主要な選挙が 有名なコンサルティング会社によって操作されていました。 ソーシャルグラフは私たちの現実世界の見方を歪め、投票を操作しています。", "privacy-matters-overlay-card-2-p-3": "SimpleX は、設計上ユーザー識別子を持たない最初のネットワークであり、この方法で既知の代替手段よりも接続グラフを保護します。", "learn-more": "さらに詳しく", "simplex-private-8-title": "メッセージのミキシング
    相関性を減らす", @@ -220,7 +220,7 @@ "protocol-1-text": "Signal、大きなプラットフォーム", "simplex-network-overlay-card-1-li-6": "P2P ネットワークは、DRDoS 攻撃に対して脆弱になる可能性があります。 クライアントがトラフィックを再ブロードキャストして増幅する可能性があり、その結果、ネットワーク全体のサービス拒否が発生する可能性があります。 SimpleX クライアントは既知の接続からのトラフィックのみを中継するため、攻撃者がネットワーク全体のトラフィックを増幅するために使用することはできません。", "if-you-already-installed-simplex-chat-for-the-terminal": "すでにターミナルに SimpleX Chat をインストールしている場合", - "docs-dropdown-8": "SimpleX ディレクトリ サービス", + "docs-dropdown-8": "SimpleX ディレクトリ", "simplex-private-card-1-point-1": "ダブルラチェットプロトコル —
    完全な前方秘匿性と侵入回復機能を備えたOTRメッセージング。", "simplex-private-card-8-point-1": "SimpleX サーバーは、低遅延の混合ノードとして機能します — 受信メッセージと送信メッセージの順序が異なります。", "simplex-unique-overlay-card-2-p-1": "SimpleXネットワークには識別子がないため、ワンタイムまたは一時的なユーザー アドレスを QR コードまたはリンクとして共有しない限り、誰もあなたに連絡することはできません。", @@ -233,9 +233,9 @@ "simplex-private-7-title": "メッセージの整合性
    検証", "privacy-matters-overlay-card-1-p-4": "SimpleXネットワークは、他のどのプラットフォームよりも接続のプライバシーを保護し、ソーシャル グラフが企業や組織に利用されることを完全に防ぎます。 SimpleX Chatアプリに予め設定されたサーバを利用している場合でも、サーバオペレータはユーザーの数や接続数を知ることはできません。", "hero-overlay-card-1-p-6": "詳細については、SimpleX ホワイトペーパーをご覧ください。", - "simplex-network-overlay-card-1-p-1": "P2P メッセージング プロトコルとアプリには、SimpleX よりも信頼性が低く、分析がより複雑になるさまざまな問題があり、 いくつかの種類の攻撃に対して脆弱です。", + "simplex-network-overlay-card-1-p-1": "P2P メッセージング プロトコルとアプリには、SimpleX よりも信頼性が低く、分析がより複雑になるさまざまな問題があり、また、いくつかの種類の攻撃に対して脆弱です。", "simplex-network-overlay-card-1-li-1": "P2P ネットワークは、メッセージをルーティングするために DHT の一部の変種に依存します。 DHT の設計では、配信保証と遅延のバランスを取る必要があります。 SimpleX は、受信者が選択したサーバーを使用して、メッセージを複数のサーバーを介して並行して冗長的に渡すことができるため、P2P よりも優れた配信保証と低い遅延の両方を備えています。 P2P ネットワークでは、メッセージはアルゴリズムによって選択されたノードを使用して、O(log N) 個のノードを順番に通過します。", - "privacy-matters-section-label": "メッセンジャーがあなたのデータにアクセスできないようにしてください!", + "privacy-matters-section-label": "メッセージアプリがあなたのデータにアクセスできないようにしてください!", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat は、サポートされているデバイスにエクスポートして転送できるポータブル暗号化データベース形式を使用して、すべてのユーザー データをクライアント デバイスにのみ保存します。", "simplex-network-3-desc": "サーバーはユーザーを接続するための一方向キューを提供しますが、ネットワーク接続グラフは表示されません— ユーザーだけがそうします。", "simplex-private-card-3-point-1": "クライアント/サーバー接続には、強力なアルゴリズムを備えた TLS 1.2/1.3 のみが使用されます。", @@ -272,7 +272,7 @@ "index-messaging-cta": "SimpleXのメッセージ機能について詳しく知る", "index-nextweb-h2": "次のWebは
    あなたのもの", "index-nextweb-p1": "SimpleXは、 アイデンティティ・連絡先・コミュニティはあなたのものであるべきだという考えに基づいています。", - "index-nextweb-p2": "オープンで分散型のネットワークで、自由で安全に人とつながり、アイデアを共有できます。", + "index-nextweb-p2": "分散型のネットワークで、自由で安全に人とつながり、アイデアを共有できます。", "index-token-h2": "続いていくコミュニティ", "index-token-p1": "コミュニティバウチャーを通じて、お気に入りのグループをサポートできます。", "index-token-p2": "バウチャーはサーバー費用に充てられ、コミュニティが自由で独立した状態を保ち続けられるようにします。", @@ -298,5 +298,9 @@ "index-publications-kuketz-title": "Mike Kuketzによるレビュー", "index-publications-optout-title": "OptOut ポッドキャストインタビュー", "send-file": "ファイルを送信", - "navbar-old-site": "旧サイト" + "navbar-old-site": "旧サイト", + "navbar-token": "トークン", + "docs-dropdown-15": "認証と再ビルド", + "index-f-droid-title": "F-Droid経由のSimpleXアプリ", + "how-secure-forward-secrecy": "前方秘匿性" } diff --git a/website/package.json b/website/package.json index 9f4a5b12e7..6ad5deab6b 100644 --- a/website/package.json +++ b/website/package.json @@ -35,6 +35,7 @@ "gray-matter": "^4.0.3", "jsdom": "^22.1.0", "lottie-web": "5.12.2", - "markdown-it": "^13.0.1" + "markdown-it": "^13.0.1", + "markdown-it-footnote": "^4.0.0" } } diff --git a/website/src/_includes/blog_previews/20260430.html b/website/src/_includes/blog_previews/20260430.html new file mode 100644 index 0000000000..fad540ed2a --- /dev/null +++ b/website/src/_includes/blog_previews/20260430.html @@ -0,0 +1,3 @@ +

    Freedom of speech needs infrastructure that protects it by design — protocols, governance and funding.

    + +

    v6.5 release brings SimpleX Channels: a new model for online publishing built for participation privacy.

    diff --git a/website/src/blog.html b/website/src/blog.html index 6ae4795baa..2e62702248 100644 --- a/website/src/blog.html +++ b/website/src/blog.html @@ -52,12 +52,15 @@ active_blog: true
    {% if blog.data.image %} {% if blog.data.imageBottom %} - + + {% if blog.data.imageLight %}{% endif %} {% elif blog.data.imageWide %} - + + {% if blog.data.imageLight %}{% endif %} {% else %} - + {% if blog.data.imageLight %}{% endif %} {% endif %} {% else %} li { + list-style-position: outside !important; + margin-bottom: 1.2rem; +} + +.footnotes-list > li::marker { + font-weight: 400; +} + +.footnotes-list > li > p { + display: inline; +} + +#article .footnotes-list .footnote-backref { + text-decoration: none; } \ No newline at end of file diff --git a/website/src/directory.html b/website/src/directory.html index 4ea42f0c3b..f6fc7991f0 100644 --- a/website/src/directory.html +++ b/website/src/directory.html @@ -20,14 +20,14 @@ active_directory: true word-break: break-word; } - #directory .entry a { + #directory .entry a.img-link { order: -1; object-fit: cover; margin-right: 16px; margin-bottom: 16px; } - #directory .entry a img { + #directory .entry a.img-link img { min-width: 104px; min-height: 104px; width: 104px; diff --git a/website/src/js/directory.js b/website/src/js/directory.js index d008370342..afaac1053f 100644 --- a/website/src/js/directory.js +++ b/website/src/js/directory.js @@ -35,10 +35,12 @@ async function initDirectory() { mode = 'live'; comparator = byActiveAtDesc; btn = liveBtn; + break; case '#new': mode = 'new'; comparator = byCreatedAtDesc; btn = newBtn; + break; default: mode = 'top'; comparator = bySortPriority; @@ -165,7 +167,7 @@ function entrySortPriority(entry) { function entryMemberCount(entry) { return entry.entryType.type == 'group' - ? (entry.entryType.summary?.currentMembers ?? 0) + ? (entry.entryType.summary?.publicMemberCount ?? entry.entryType.summary?.currentMembers ?? 0) : 0 } @@ -263,6 +265,13 @@ function displayEntries(entries) { }, 0); } + if (entryType?.groupType) { + const noteElement = document.createElement('p'); + noteElement.innerHTML = 'You need SimpleX Chat app v6.5 to join.'; + noteElement.className = 'text-sm'; + textContainer.appendChild(noteElement); + } + const entryTimestamp = currentSortMode === 'new' && entry.createdAt ? showCreatedOn(entry.createdAt) : entry.activeAt @@ -278,7 +287,8 @@ function displayEntries(entries) { const memberCount = entryMemberCount(entry); if (typeof memberCount == 'number' && memberCount > 0) { const memberCountElement = document.createElement('p'); - memberCountElement.textContent = `${memberCount} members`; + const isChannel = entryType?.groupType === 'channel'; + memberCountElement.textContent = `${memberCount} ${isChannel ? 'subscribers' : 'members'}`; memberCountElement.className = 'text-sm'; textContainer.appendChild(memberCountElement); } @@ -291,6 +301,7 @@ function displayEntries(entries) { } const imgLinkElement = document.createElement('a'); + imgLinkElement.className = 'img-link'; const groupLinkUri = groupLink.connShortLink ?? groupLink.connFullLink try { imgLinkElement.href = platformSimplexUri(groupLinkUri);