diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml new file mode 100644 index 0000000000..ce75b7a57c --- /dev/null +++ b/.github/actions/prepare-build/action.yml @@ -0,0 +1,47 @@ +name: "Prebuilt steps for build" +description: "Reusable steps for multiple jobs" +inputs: + java_ver: + required: true + description: "Java version to install" + ghc_ver: + required: true + description: "GHC version to install" + github_ref: + required: true + description: "Git reference" + os: + required: true + description: "Target OS" + cache_path: + required: false + default: "~/.cabal/store" + description: "Cache path" + cabal_ver: + required: false + default: 3.10.1.0 + description: "GHC version to install" +runs: + using: "composite" + steps: + - name: Setup Haskell + uses: simplex-chat/setup-haskell-action@v2 + with: + ghc-version: ${{ inputs.ghc_ver }} + cabal-version: ${{ inputs.cabal_ver }} + + - name: Setup Java + if: startsWith(inputs.github_ref, 'refs/tags/v') + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: ${{ inputs.java_ver }} + cache: 'gradle' + + - name: Restore cached build + uses: actions/cache@v4 + with: + path: | + ${{ inputs.cache_path }} + dist-newstyle + key: ${{ inputs.os }}-ghc${{ inputs.ghc_ver }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} diff --git a/.github/actions/prepare-release/action.yml b/.github/actions/prepare-release/action.yml new file mode 100644 index 0000000000..e0d32bd596 --- /dev/null +++ b/.github/actions/prepare-release/action.yml @@ -0,0 +1,39 @@ +name: "Upload binary and update hash" +description: "Reusable steps for multiple jobs" +inputs: + bin_path: + required: true + description: "Path to binary to upload" + bin_name: + required: true + description: "Name of uploaded binary" + bin_hash: + required: true + description: "Message with SHA to include in release" + github_ref: + required: true + description: "Github reference" + github_token: + required: true + description: "Github token" +runs: + using: "composite" + steps: + - name: Upload file with specific name + if: startsWith(inputs.github_ref, 'refs/tags/v') + uses: simplex-chat/upload-release-action@v2 + with: + repo_token: ${{ inputs.github_token }} + file: ${{ inputs.bin_path }} + asset_name: ${{ inputs.bin_name }} + tag: ${{ inputs.github_ref }} + + - name: Add hash to release notes + if: startsWith(inputs.github_ref, 'refs/tags/v') + uses: simplex-chat/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + with: + append_body: true + body: | + ${{ inputs.bin_hash }} diff --git a/.github/actions/swap/action.yml b/.github/actions/swap/action.yml new file mode 100644 index 0000000000..87d670b147 --- /dev/null +++ b/.github/actions/swap/action.yml @@ -0,0 +1,44 @@ +name: 'Set Swap Space' +description: 'Add moar swap' +branding: + icon: 'crop' + color: 'orange' +inputs: + swap-size-gb: + description: 'Swap space to create, in Gigabytes.' + required: false + default: '10' +runs: + using: "composite" + steps: + - name: Swap space report before modification + shell: bash + run: | + echo "Memory and swap:" + free -h + echo + swapon --show + echo + - name: Set Swap + shell: bash + run: | + export SWAP_FILE=$(swapon --show=NAME | tail -n 1) + echo "Swap file: $SWAP_FILE" + if [ -z "$SWAP_FILE" ]; then + SWAP_FILE=/opt/swapfile + else + sudo swapoff $SWAP_FILE + sudo rm $SWAP_FILE + fi + sudo fallocate -l ${{ inputs.swap-size-gb }}G $SWAP_FILE + sudo chmod 600 $SWAP_FILE + sudo mkswap $SWAP_FILE + sudo swapon $SWAP_FILE + - name: Swap space report after modification + shell: bash + run: | + echo "Memory and swap:" + free -h + echo + swapon --show + echo diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b9cc7f770..ca1bc79510 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,24 +5,75 @@ on: branches: - master - stable - - users tags: - "v*" - "!*-fdroid" - "!*-armv7a" pull_request: + paths-ignore: + - "apps/ios" + - "apps/multiplatform" + - "blog" + - "docs" + - "fastlane" + - "images" + - "packages" + - "website" + - "README.md" + - "PRIVACY.md" + +# This workflow uses custom actions (prepare-build and prepare-release) defined in: +# +# .github/actions/ +# ├── prepare-build +# │ └── action.yml +# └── prepare-release +# └── action.yml + +# Important! +# Do not use always(), it makes build unskippable. +# See: https://github.com/actions/runner/issues/1846#issuecomment-1246102753 jobs: - prepare-release: - if: startsWith(github.ref, 'refs/tags/v') + +# ============================= +# Global variables +# ============================= + +# That is the only and less hacky way to setup global variables +# to use in strategy matrix (env:/YAML anchors doesn't work). +# See: https://github.com/orgs/community/discussions/56787#discussioncomment-6041789 +# https://github.com/actions/runner/issues/1182 +# https://stackoverflow.com/a/77549656 + + variables: + runs-on: ubuntu-latest + outputs: + GHC_VER: 9.6.3 + JAVA_VER: 17 + steps: + - name: Dummy job when we have just simple variables + if: false + run: echo + +# ============================= +# Create release +# ============================= + +# Create release, but only if it's triggered by tag push. +# On pull requests/commits push, this job will always complete. + + maybe-release: runs-on: ubuntu-latest steps: - name: Clone project + if: startsWith(github.ref, 'refs/tags/v') uses: actions/checkout@v3 - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@v4 + if: startsWith(github.ref, 'refs/tags/v') + uses: simplex-chat/release-changelog-builder-action@v5 with: configuration: .github/changelog_conf.json failOnError: true @@ -32,7 +83,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create release - uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/v') + uses: simplex-chat/action-gh-release@v2 with: body: ${{ steps.build_changelog.outputs.changelog }} prerelease: true @@ -42,271 +94,375 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - build: - name: build-${{ matrix.os }}-${{ matrix.ghc }} - if: always() - needs: prepare-release - runs-on: ${{ matrix.os }} +# ========================= +# Linux Build +# ========================= + + build-linux: + name: "ubuntu-${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + needs: [maybe-release, variables] + runs-on: ubuntu-${{ matrix.os }} strategy: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: 22.04 ghc: "8.10.7" - cache_path: ~/.cabal/store - - os: ubuntu-20.04 - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-ubuntu-20_04-x86-64 - desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb - - os: ubuntu-22.04 - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-ubuntu-22_04-x86-64 + should_run: ${{ !(github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} + - os: 22.04 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-ubuntu-22_04-x86-64 desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb - - os: macos-latest - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-macos-aarch64 - desktop_asset_name: simplex-desktop-macos-aarch64.dmg - - os: macos-13 - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-macos-x86-64 - desktop_asset_name: simplex-desktop-macos-x86_64.dmg - - os: windows-latest - ghc: "9.6.3" - cache_path: C:/cabal - asset_name: simplex-chat-windows-x86-64 - desktop_asset_name: simplex-desktop-windows-x86_64.msi + should_run: true + - os: 24.04 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-ubuntu-24_04-x86-64 + desktop_asset_name: simplex-desktop-ubuntu-24_04-x86_64.deb + should_run: true steps: - - name: Configure pagefile (Windows) - if: matrix.os == 'windows-latest' - uses: al-cheb/configure-pagefile-action@v1.3 - with: - minimum-size: 16GB - maximum-size: 16GB - disk-root: "C:" - - - name: Clone project + - name: Checkout Code + if: matrix.should_run == true uses: actions/checkout@v3 - - name: Setup Haskell - uses: haskell/actions/setup@v2 + - name: Setup swap + if: matrix.ghc == '8.10.7' && matrix.should_run == true + uses: ./.github/actions/swap with: - ghc-version: ${{ matrix.ghc }} - cabal-version: "3.10.1.0" + swap-size-gb: 30 + + # Otherwise we run out of disk space with Docker build + - name: Free disk space + if: matrix.should_run == true + shell: bash + run: ./scripts/ci/linux_util_free_space.sh - name: Restore cached build - id: restore_cache - uses: actions/cache/restore@v3 + if: matrix.should_run == true + uses: actions/cache@v4 with: path: | - ${{ matrix.cache_path }} + ~/.cabal/store dist-newstyle - key: ${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} + key: ubuntu-${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} - # / Unix + - name: Set up Docker Buildx + if: matrix.should_run == true + uses: simplex-chat/docker-setup-buildx-action@v3 - - name: Unix prepare cabal.project.local for Mac - if: matrix.os == 'macos-latest' + - name: Build and cache Docker image + if: matrix.should_run == true + uses: simplex-chat/docker-build-push-action@v6 + with: + context: . + load: true + file: Dockerfile.build + tags: build/${{ matrix.os }}:latest + build-args: | + TAG=${{ matrix.os }} + GHC=${{ matrix.ghc }} + + # Docker needs these flags for AppImage build: + # --device /dev/fuse + # --cap-add SYS_ADMIN + # --security-opt apparmor:unconfined + - name: Start container + if: matrix.should_run == true shell: bash run: | - echo "ignore-project: False" >> cabal.project.local - echo "package simplexmq" >> cabal.project.local - echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local - echo "" >> cabal.project.local - echo "package direct-sqlcipher" >> cabal.project.local - echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local - echo " flags: +openssl" >> cabal.project.local + docker run -t -d \ + --device /dev/fuse \ + --cap-add SYS_ADMIN \ + --security-opt apparmor:unconfined \ + --name builder \ + -v ~/.cabal:/root/.cabal \ + -v /home/runner/work/_temp:/home/runner/work/_temp \ + -v ${{ github.workspace }}:/project \ + build/${{ matrix.os }}:latest - - name: Unix prepare cabal.project.local for Mac - if: matrix.os == 'macos-13' - shell: bash - run: | - echo "ignore-project: False" >> cabal.project.local - echo "package simplexmq" >> cabal.project.local - echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local - echo "" >> cabal.project.local - echo "package direct-sqlcipher" >> cabal.project.local - echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local - echo " flags: +openssl" >> cabal.project.local - - - name: Install AppImage dependencies - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - run: sudo apt install -y desktop-file-utils - - - name: Install pkg-config for Mac - if: matrix.os == 'macos-latest' || matrix.os == 'macos-13' - run: brew install pkg-config - - - name: Unix prepare cabal.project.local for Ubuntu - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04' + - name: Prepare cabal.project.local + if: matrix.should_run == true shell: bash run: | echo "ignore-project: False" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - - name: Unix build CLI - id: unix_cli_build - if: matrix.os != 'windows-latest' + # chmod/git commands are used to workaround permission issues when cache is restored + - name: Build CLI + if: matrix.should_run == true + shell: docker exec -t builder sh -eu {0} + run: | + chmod -R 777 dist-newstyle ~/.cabal && git config --global --add safe.directory '*' + cabal clean + cabal update + cabal build -j --enable-tests + mkdir -p /out + for i in simplex-chat simplex-chat-test; do + bin=$(find /project/dist-newstyle -name "$i" -type f -executable) + chmod +x "$bin" + mv "$bin" /out/ + done + strip /out/simplex-chat + + - name: Copy tests from container + if: matrix.should_run == true shell: bash run: | - cabal build --enable-tests - path=$(cabal list-bin simplex-chat) - echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + docker cp builder:/out/simplex-chat-test . - - name: Unix upload CLI binary to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.unix_cli_build.outputs.bin_path }} - asset_name: ${{ matrix.asset_name }} - tag: ${{ github.ref }} - - - name: Unix update CLI binary hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.unix_cli_build.outputs.bin_hash }} - - - name: Setup Java - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name - uses: actions/setup-java@v3 - with: - distribution: 'corretto' - java-version: '17' - cache: 'gradle' - - - name: Linux build desktop - id: linux_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') + - name: Copy CLI from container and prepare it + id: linux_cli_prepare + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash + run: | + docker cp builder:/out/simplex-chat ./${{ matrix.cli_asset_name }} + path="${{ github.workspace }}/${{ matrix.cli_asset_name }}" + echo "bin_path=$path" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + + - name: Upload CLI + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true + uses: ./.github/actions/prepare-release + with: + bin_path: ${{ steps.linux_cli_prepare.outputs.bin_path }} + bin_name: ${{ matrix.cli_asset_name }} + bin_hash: ${{ steps.linux_cli_prepare.outputs.bin_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Desktop + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true + shell: docker exec -t builder sh -eu {0} run: | scripts/desktop/build-lib-linux.sh cd apps/multiplatform ./gradlew packageDeb - path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb) - echo "package_path=$path" >> $GITHUB_OUTPUT - echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Linux make AppImage - id: linux_appimage_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' + - name: Prepare Desktop + id: linux_desktop_build + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | - scripts/desktop/make-appimage-linux.sh - path=$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage) - echo "appimage_path=$path" >> $GITHUB_OUTPUT - echo "appimage_hash=$(echo SHA2-512\(simplex-desktop-x86_64.AppImage\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/deb/simplex_*_amd64.deb ) + echo "package_path=$path" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Mac build desktop + - name: Upload Desktop + uses: ./.github/actions/prepare-release + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true + with: + bin_path: ${{ steps.linux_desktop_build.outputs.package_path }} + bin_name: ${{ matrix.desktop_asset_name }} + bin_hash: ${{ steps.linux_desktop_build.outputs.package_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build AppImage + if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true + shell: docker exec -t builder sh -eu {0} + run: | + scripts/desktop/make-appimage-linux.sh + + - name: Prepare AppImage + id: linux_appimage_build + if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true + shell: bash + run: | + path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/*imple*.AppImage) + echo "appimage_path=$path" >> $GITHUB_OUTPUT + echo "appimage_hash=$(echo SHA2-256\(simplex-desktop-x86_64.AppImage\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + + - name: Upload AppImage + if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true + uses: ./.github/actions/prepare-release + with: + bin_path: ${{ steps.linux_appimage_build.outputs.appimage_path }} + bin_name: "simplex-desktop-x86_64.AppImage" + bin_hash: ${{ steps.linux_appimage_build.outputs.appimage_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix permissions for cache + if: matrix.should_run == true + shell: bash + run: | + sudo chmod -R 777 dist-newstyle ~/.cabal + sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal + + - name: Run tests + if: matrix.should_run == true + timeout-minutes: 120 + shell: bash + run: | + i=1 + attempts=1 + ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3 + while [ "$i" -le "$attempts" ]; do + if ./simplex-chat-test; then + break + else + echo "Attempt $i failed, retrying..." + i=$((i + 1)) + sleep 1 + fi + done + if [ "$i" -gt "$attempts" ]; then + echo "All "$attempts" attempts failed." + exit 1 + fi + +# ========================= +# MacOS Build +# ========================= + + build-macos: + name: "${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + needs: [maybe-release, variables] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-macos-aarch64 + desktop_asset_name: simplex-desktop-macos-aarch64.dmg + openssl_dir: "/opt/homebrew/opt" + - os: macos-13 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-macos-x86-64 + desktop_asset_name: simplex-desktop-macos-x86_64.dmg + openssl_dir: "/usr/local/opt" + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Prepare build + uses: ./.github/actions/prepare-build + with: + java_ver: ${{ needs.variables.outputs.JAVA_VER }} + ghc_ver: ${{ matrix.ghc }} + os: ${{ matrix.os }} + github_ref: ${{ github.ref }} + + - name: Install OpenSSL + run: brew install openssl@3.0 + + - name: Prepare cabal.project.local + shell: bash + run: | + echo "ignore-project: False" >> cabal.project.local + echo "package simplexmq" >> cabal.project.local + echo " extra-include-dirs: ${{ matrix.opnessl_dir }}/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: ${{ matrix.openssl_dir}}/openssl@3.0/lib" >> cabal.project.local + echo "" >> cabal.project.local + echo "package direct-sqlcipher" >> cabal.project.local + echo " extra-include-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/lib" >> cabal.project.local + echo " flags: +openssl" >> cabal.project.local + + - name: Build CLI + id: mac_cli_build + shell: bash + run: | + cabal build -j --enable-tests + path=$(cabal list-bin simplex-chat) + echo "bin_path=$path" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + + - name: Upload CLI + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release + with: + bin_path: ${{ steps.mac_cli_build.outputs.bin_path }} + bin_name: ${{ matrix.cli_asset_name }} + bin_hash: ${{ steps.mac_cli_build.outputs.bin_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Desktop id: mac_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') + if: startsWith(github.ref, 'refs/tags/v') shell: bash env: APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }} APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }} APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }} run: | - scripts/build-desktop-mac.sh + scripts/ci/build-desktop-mac.sh path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg) echo "package_path=$path" >> $GITHUB_OUTPUT - echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Linux upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') - uses: svenstaro/upload-release-action@v2 + - name: Upload Desktop + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.linux_desktop_build.outputs.package_path }} - asset_name: ${{ matrix.desktop_asset_name }} - tag: ${{ github.ref }} + bin_path: ${{ steps.mac_desktop_build.outputs.package_path }} + bin_name: ${{ matrix.desktop_asset_name }} + bin_hash: ${{ steps.mac_desktop_build.outputs.package_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Linux update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.linux_desktop_build.outputs.package_hash }} - - - name: Linux upload AppImage to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.linux_appimage_build.outputs.appimage_path }} - asset_name: simplex-desktop-x86_64.AppImage - tag: ${{ github.ref }} - - - name: Linux update AppImage hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.linux_appimage_build.outputs.appimage_hash }} - - - name: Mac upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.mac_desktop_build.outputs.package_path }} - asset_name: ${{ matrix.desktop_asset_name }} - tag: ${{ github.ref }} - - - name: Mac update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.mac_desktop_build.outputs.package_hash }} - - - name: Cache unix build - uses: actions/cache/save@v3 - if: matrix.os != 'windows-latest' - with: - path: | - ${{ matrix.cache_path }} - dist-newstyle - key: ${{ steps.restore_cache.outputs.cache-primary-key }} - - - name: Unix test - if: matrix.os != 'windows-latest' - timeout-minutes: 40 + - name: Run tests + timeout-minutes: 120 shell: bash - run: cabal test --test-show-details=direct + run: | + i=1 + attempts=1 + ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3 + while [ "$i" -le "$attempts" ]; do + if cabal test --test-show-details=direct; then + break + else + echo "Attempt $i failed, retrying..." + i=$((i + 1)) + sleep 1 + fi + done + if [ "$i" -gt "$attempts" ]; then + echo "All "$attempts" attempts failed." + exit 1 + fi - # Unix / +# ========================= +# Windows Build +# ========================= - # / Windows + build-windows: + name: "${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + needs: [maybe-release, variables] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-windows-x86-64 + desktop_asset_name: simplex-desktop-windows-x86_64.msi + steps: + - name: Checkout Code + uses: actions/checkout@v3 - # * In powershell multiline commands do not fail if individual commands fail - https://github.community/t/multiline-commands-on-windows-do-not-fail-if-individual-commands-fail/16753 - # * And GitHub Actions does not support parameterizing shell in a matrix job - https://github.community/t/using-matrix-to-specify-shell-is-it-possible/17065 + - name: Prepare build + uses: ./.github/actions/prepare-build + with: + java_ver: ${{ needs.variables.outputs.JAVA_VER }} + ghc_ver: ${{ matrix.ghc }} + os: ${{ matrix.os }} + cache_path: "C:/cabal" + github_ref: ${{ github.ref }} + - name: Configure pagefile (Windows) + uses: simplex-chat/configure-pagefile-action@v1.4 + with: + minimum-size: 16GB + maximum-size: 16GB + disk-root: "C:" + - name: 'Setup MSYS2' - if: matrix.os == 'windows-latest' - uses: msys2/setup-msys2@v2 + uses: simplex-chat/setup-msys2@v2 with: msystem: ucrt64 update: true @@ -318,15 +474,14 @@ jobs: toolchain:p cmake:p - - - name: Windows build - id: windows_build - if: matrix.os == 'windows-latest' + # rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing + - name: Build CLI + id: windows_cli_build shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/prepare-openssl-windows.sh - openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-1.1.1w | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g') + openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-3.0.15 | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g') rm cabal.project.local 2>/dev/null || true echo "ignore-project: False" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local @@ -336,70 +491,42 @@ jobs: rm -rf dist-newstyle/src/direct-sq* sed -i "s/, unix /--, unix /" simplex-chat.cabal - cabal build --enable-tests + cabal build -j --enable-tests rm -rf dist-newstyle/src/direct-sq* path=$(cabal list-bin simplex-chat | tail -n 1) echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Windows upload CLI binary to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: svenstaro/upload-release-action@v2 + - name: Upload CLI + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.windows_build.outputs.bin_path }} - asset_name: ${{ matrix.asset_name }} - tag: ${{ github.ref }} + bin_path: ${{ steps.windows_cli_build.outputs.bin_path }} + bin_name: ${{ matrix.cli_asset_name }} + bin_hash: ${{ steps.windows_cli_build.outputs.bin_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Windows update CLI binary hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.windows_build.outputs.bin_hash }} - - - name: Windows build desktop + - name: Build Desktop id: windows_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' + if: startsWith(github.ref, 'refs/tags/v') shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/build-lib-windows.sh cd apps/multiplatform ./gradlew 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 - echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Windows upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: svenstaro/upload-release-action@v2 + - name: Upload Desktop + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.windows_desktop_build.outputs.package_path }} - asset_name: ${{ matrix.desktop_asset_name }} - tag: ${{ github.ref }} - - - name: Windows update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.windows_desktop_build.outputs.package_hash }} - - - name: Cache windows build - uses: actions/cache/save@v3 - if: matrix.os == 'windows-latest' - with: - path: | - ${{ matrix.cache_path }} - dist-newstyle - key: ${{ steps.restore_cache.outputs.cache-primary-key }} - - # Windows / + bin_path: ${{ steps.windows_desktop_build.outputs.package_path }} + bin_name: ${{ matrix.desktop_asset_name }} + bin_hash: ${{ steps.windows_desktop_build.outputs.package_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reproduce-schedule.yml b/.github/workflows/reproduce-schedule.yml new file mode 100644 index 0000000000..7de44addc7 --- /dev/null +++ b/.github/workflows/reproduce-schedule.yml @@ -0,0 +1,45 @@ +name: Reproduce latest release + +on: + workflow_dispatch: + schedule: + - cron: '0 2 * * *' # every day at 02:00 night + +jobs: + reproduce: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get latest release + shell: bash + run: | + curl --proto '=https' \ + --tlsv1.2 \ + -sSf -L \ + 'https://api.github.com/repos/simplex-chat/simplex-chat/releases/latest' \ + 2>/dev/null | \ + grep -i "tag_name" | \ + awk -F \" '{print "TAG="$4}' >> $GITHUB_ENV + + - name: Execute reproduce script + run: | + ${GITHUB_WORKSPACE}/scripts/reproduce-builds.sh "$TAG" + + - name: Check if build has been reproduced + env: + url: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_URL }} + user: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_USER }} + pass: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_PASS }} + run: | + if [ -f "${GITHUB_WORKSPACE}/$TAG/_sha256sums" ]; then + exit 0 + else + curl --proto '=https' --tlsv1.2 -sSf \ + -u "${user}:${pass}" \ + -H 'Content-Type: application/json' \ + -d '{"title": "👾 GitHub: Runner", "description": "⛔️ '"$TAG"' did not reproduce."}' \ + "$url" + exit 1 + fi diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 7fc66308f8..5fbe8293bc 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -10,6 +10,7 @@ on: - blog/** - docs/** - .github/workflows/web.yml + - PRIVACY.md jobs: build: @@ -32,7 +33,7 @@ jobs: ./website/web.sh - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: simplex-chat/actions-gh-pages@v3 with: publish_dir: ./website/_site github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e3ea5d267b..645b55ec9d 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ website/package/generated* # Ignore build tool output, e.g. code coverage website/.nyc_output/ website/coverage/ +result # Ignore API documentation website/api-docs/ diff --git a/Dockerfile b/Dockerfile index 6c60195f97..cdcbc40d7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN cp ./scripts/cabal.project.local.linux ./cabal.project.local # Compile simplex-chat RUN cabal update -RUN cabal build exe:simplex-chat +RUN cabal build exe:simplex-chat --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' # Strip the binary from debug symbols to reduce size RUN bin=$(find /project/dist-newstyle -name "simplex-chat" -type f -executable) && \ diff --git a/Dockerfile.build b/Dockerfile.build new file mode 100644 index 0000000000..76bb1127f2 --- /dev/null +++ b/Dockerfile.build @@ -0,0 +1,92 @@ +# syntax=docker/dockerfile:1.7.0-labs +ARG TAG=24.04 +FROM ubuntu:${TAG} AS build + +### Build stage + +ARG GHC=9.6.3 +ARG CABAL=3.10.1.0 +ARG JAVA=17 + +ENV TZ=Etc/UTC \ + DEBIAN_FRONTEND=noninteractive + +# Install curl, git and and simplex-chat dependencies +RUN apt-get update && \ + apt-get install -y curl \ + libpq-dev \ + git \ + sqlite3 \ + libsqlite3-dev \ + build-essential \ + libgmp3-dev \ + zlib1g-dev \ + llvm \ + cmake \ + llvm-dev \ + libnuma-dev \ + libssl-dev \ + desktop-file-utils \ + patchelf \ + ca-certificates \ + zip \ + wget \ + fuse3 \ + file \ + appstream \ + gpg \ + unzip &&\ + ln -s /bin/fusermount /bin/fusermount3 || : + +# Install Java Coretto +# Required, because official Java in Ubuntu +# depends on libjpeg.so.8 and liblcms2.so.2 which are NOT copied into final +# /usr/lib/runtime/lib directory and I do not have time to figure out gradle.kotlin +# to fix this :( +RUN curl --proto '=https' --tlsv1.2 -sSf 'https://apt.corretto.aws/corretto.key' | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg &&\ + echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" > /etc/apt/sources.list.d/corretto.list &&\ + apt update &&\ + apt install -y java-${JAVA}-amazon-corretto-jdk + +# Specify bootstrap Haskell versions +ENV BOOTSTRAP_HASKELL_GHC_VERSION=${GHC} +ENV BOOTSTRAP_HASKELL_CABAL_VERSION=${CABAL} + +# Do not install Stack +ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK=true +ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=true + +# Install ghcup +RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh + +# Adjust PATH +ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH" + +# Set both as default +RUN ghcup set ghc "${GHC}" && \ + ghcup set cabal "${CABAL}" + +#===================== +# Install Android SDK +#===================== +ARG SDK_VERSION=13114758 + +ENV SDK_VERSION=$SDK_VERSION \ + ANDROID_HOME=/root + +RUN curl -L -o tools.zip "https://dl.google.com/android/repository/commandlinetools-linux-${SDK_VERSION}_latest.zip" && \ + unzip tools.zip && rm tools.zip && \ + mv cmdline-tools tools && mkdir "$ANDROID_HOME/cmdline-tools" && mv tools "$ANDROID_HOME/cmdline-tools/" && \ + ln -s "$ANDROID_HOME/cmdline-tools/tools" "$ANDROID_HOME/cmdline-tools/latest" + +ENV PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin" + +# https://askubuntu.com/questions/885658/android-sdk-repositories-cfg-could-not-be-loaded +RUN mkdir -p ~/.android ~/.gradle && \ + touch ~/.android/repositories.cfg && \ + echo 'org.gradle.console=plain' > ~/.gradle/gradle.properties &&\ + yes | sdkmanager --licenses >/dev/null + +ENV PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools + +WORKDIR /project diff --git a/PRIVACY.md b/PRIVACY.md index 66dff0e807..18e5539726 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -3,63 +3,93 @@ layout: layouts/privacy.html permalink: /privacy/index.html --- -# SimpleX Chat Privacy Policy and Conditions of Use +# SimpleX Chat Operators Privacy Policy and Conditions of Use -SimpleX Chat is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability. +## Summary -SimpleX Chat communication protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX Chat apps allow their users to send messages and files via relay server infrastructure. Relay server owners and providers do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not provide any user accounts. +[Introduction](#introduction) and [General principles](#general-principles) cover SimpleX Chat network design, the network operators, and the principles of privacy and security provided by SimpleX network. + +[Privacy policy](#privacy-policy) covers: +- data stored only on your device - [your profiles](#user-profiles), delivered [messages and files](#messages-and-files). You can transfer this information to another device, and you are responsible for its preservation - if you delete the app it will be lost. +- [private message delivery](#private-message-delivery) that protects your IP address and connection graph from the destination servers. +- [undelivered messages and files](#storage-of-messages-and-files-on-the-servers) stored on the servers. +- [how users connect](#connections-with-other-users) without any user profile identifiers. +- [iOS push notifications](#ios-push-notifications) privacy limitations. +- [user support](#user-support), [SimpleX directory](#simplex-directory) and [any other data](#another-information-stored-on-the-servers) that may be stored on the servers. +- [preset server operators](#preset-server-operators) and the [information they may share](#information-preset-server-operators-may-share). +- [source code license](#source-code-license) and [updates to this document](#updates). + +[Conditions of Use](#conditions-of-use-of-software-and-infrastructure) are the conditions you need to accept to use SimpleX Chat applications and the relay servers of preset operators. Their purpose is to protect the users and preset server operators. + +*Please note*: this summary and any links in this document are provided for information only - they are not a part of the Privacy Policy and Conditions of Use. + +## Introduction + +SimpleX Chat (also referred to as SimpleX) is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability. + +SimpleX messaging protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX apps allow their users to send messages and files via relay server infrastructure. Relay server owners and operators do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not host user accounts. Double ratchet algorithm has such important properties as [forward secrecy](/docs/GLOSSARY.md#forward-secrecy), sender [repudiation](/docs/GLOSSARY.md#) and break-in recovery (also known as [post-compromise security](/docs/GLOSSARY.md#post-compromise-security)). -If you believe that any part of this document is not aligned with our mission or values, please raise it with us via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). +If you believe that any part of this document is not aligned with SimpleX network mission or values, please raise it via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). ## Privacy Policy -SimpleX Chat Ltd uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](/docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](/docs/GLOSSARY.md#man-in-the-middle-attack), unlike most other communication platforms, services and networks. +### General principles -SimpleX Chat software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having any kind of addresses or other identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications. +SimpleX network software uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](/docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption is protected from being compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](/docs/GLOSSARY.md#man-in-the-middle-attack). -SimpleX Chat software is similar in its design approach to email clients and browsers - it allows you to have full control of your data and freely choose the relay server providers, in the same way you choose which website or email provider to use, or use your own relay servers, simply by changing the configuration of the client software. The only current restriction to that is Apple push notifications - at the moment they can only be delivered via the preset servers that we operate, as explained below. We are exploring the solutions to deliver push notifications to iOS devices via other providers or users' own servers. +SimpleX software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications. -While SimpleX Chat Ltd is not a communication service provider, and provide public preset relays "as is", as experimental, without any guarantees of availability or data retention, we are committed to maintain a high level of availability, reliability and security of these preset relays. We will be adding alternative preset infrastructure providers to the software in the future, and you will continue to be able to use any other providers or your own servers. +SimpleX software is similar in its design approach to email clients and browsers - it allows you to have full control of your data and freely choose the relay server operators, in the same way you choose which website or email provider to use, or use your own relay servers, simply by changing the configuration of the client software. The only current restriction to that is Apple push notifications - at the moment they can only be delivered via the servers operated by SimpleX Chat Ltd, as explained below. We are exploring the solutions to deliver push notifications to iOS devices via other providers or users' own servers. -We see users and data sovereignty, and device and provider portability as critically important properties for any communication system. +SimpleX network operators are not communication service provider, and provide public relays "as is", as experimental, without any guarantees of availability or data retention. The operators of the relay servers preset in the app ("Preset Server Operators"), including SimpleX Chat Ltd, are committed to maintain a high level of availability, reliability and security. SimpleX client apps can have multiple preset relay server operators that you can opt-in or opt-out of using. You are and will continue to be able to use any other operators or your own servers. -SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 – see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). +SimpleX network design is based on the principles of users and data sovereignty, and device and operator portability. + +The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 – see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). + +The cryptographic review of SimpleX protocols design was done in July 2024 by Trail of Bits – see [the announcement](/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). ### Your information #### User profiles -Servers used by SimpleX Chat apps do not create, store or identify user profiles. The profiles you can create in the app are local to your device, and can be removed at any time via the app. +Servers used by SimpleX Chat apps do not create, store or identify user chat profiles. The profiles you can create in the app are local to your device, and can be removed at any time via the app. -When you create the local profile, no records are created on any of the relay servers, and infrastructure providers, whether SimpleX Chat Ltd or any other, have no access to any part of your information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all your data and the private connections you created with other software users. +When you create the local profile, no records are created on any of the relay servers, and infrastructure operators, whether preset in the app or any other, have no access to any part of your information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all your data and the private connections you created with other software users. You can transfer the profile to another device by creating a backup of the app data and restoring it on the new device, but you cannot use more than one device with the copy of the same profile at the same time - it will disrupt any active conversations on either or both devices, as a security property of end-to-end encryption. #### Messages and Files -SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 8mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](/docs/GLOSSARY.md#key-exchange) happens out-of-band. +SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 4mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](/docs/GLOSSARY.md#key-exchange) happens out-of-band. Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline, messaging relay servers temporarily store end-to-end encrypted messages – you can configure which relay servers are used to receive the messages from the new contacts, and you can manually change them for the existing contacts too. -You do not have control over which servers are used to send messages to your contacts - they are chosen by them. To send messages your client needs to connect to these servers, therefore the servers chosen by your contacts can observe your IP address. You can use VPN or some overlay network (e.g., Tor) to hide your IP address from the servers chosen by your contacts. In the near future we will add the layer in the messaging protocol that will route sent message via the relays chosen by you as well. +#### Private message delivery -The messages are permanently removed from the used relay servers as soon as they are delivered, as long as these servers used unmodified published code. Undelivered messages are deleted after the time that is configured in the messaging servers you use (21 days for preset messaging servers). +You do not have control over which servers are used to send messages to your contacts - these servers are chosen by your contacts. To send messages your client by default uses configured servers to forward messages to the destination servers, thus protecting your IP address from the servers chosen by your contacts. + +In case you use preset servers of more than one operator, the app will prefer to use a server of an operator different from the operator of the destination server to forward messages, preventing destination server to correlate messages as belonging to one client. + +You can additionally use VPN or some overlay network (e.g., Tor) to hide your IP address from the servers chosen by you. + +*Please note*: the clients allow changing configuration to connect to the destination servers directly. It is not recommended - if you make such change, your IP address will be visible to the destination servers. + +#### Storage of messages and files on the servers + +The messages are removed from the relay servers as soon as all messages of the file they were stored in are delivered and saving new messages switches to another file, as long as these servers use unmodified published code. Undelivered messages are also marked as delivered after the time that is configured in the messaging servers you use (21 days for preset messaging servers). The files are stored on file relay servers for the time configured in the relay servers you use (48 hours for preset file servers). -If a messaging servers are restarted, the encrypted message can be stored in a backup file until it is overwritten by the next restart (usually within 1 week for preset relay servers). - -As this software is fully open-source and provided under AGPLv3 license, all infrastructure providers and owners, and the developers of the client and server applications who use the SimpleX Chat source code, are required to publish any changes to this software under the same AGPLv3 license - including any modifications to the provided servers. - -In addition to the AGPLv3 license terms, SimpleX Chat Ltd is committed to the software users that the preset relays that we provide via the apps will always be compiled from the [published open-source code](https://github.com/simplex-chat/simplexmq), without any modifications. +The encrypted messages can be stored for some time after they are delivered or expired (because servers use append-only logs for message storage). This time varies, and may be longer in connections with fewer messages, but it is usually limited to 1 month, including any backup storage. #### Connections with other users -When you create a connection with another user, two messaging queues (you can think about them as mailboxes) are created on messaging relay servers (chosen by you and your contact each), that can be the preset servers or the servers that you and your contact configured in the app. SimpleX messaging protocol uses separate queues for direct and response messages, and the apps prefer to create these queues on two different relay servers for increased privacy, in case you have more than one relay server configured in the app, which is the default. +When you create a connection with another user, two messaging queues (you can think about them as mailboxes) are created on messaging relay servers (chosen by you and your contact each), that can be the preset servers or the servers that you and your contact configured in the app. SimpleX messaging protocol uses separate queues for direct and response messages, and the apps prefer to create these queues on two different relay servers, or, if available, the relays of two different operators, for increased privacy, in case you have more than one relay server configured in the app, which is the default. -SimpleX relay servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow infrastructure owners and providers to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages. +Preset and unmodified SimpleX relay servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow infrastructure owners and operators to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages. #### Connection links privacy @@ -75,6 +105,8 @@ You can always safely replace the initial part of the link `https://simplex.chat #### iOS Push Notifications +This section applies only to the notification servers operated by SimpleX Chat Ltd. + When you choose to use instant push notifications in SimpleX iOS app, because the design of push notifications requires storing the device token on notification server, the notifications server can observe how many messaging queues your device has notifications enabled for, and approximately how many messages are sent to each queue. Preset notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who sends messages to you. Apple push notifications servers can only observe how many notifications are sent to you, but not from how many contacts, or from which messaging relays, as notifications are delivered to your device end-to-end encrypted by one of the preset notification servers - these notifications only contain end-to-end encrypted metadata, not even encrypted message content, and they look completely random to Apple push notification servers. @@ -83,93 +115,132 @@ You can read more about the design of iOS push notifications [here](./blog/20220 #### Another information stored on the servers -Additional technical information can be stored on our servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX Chat design limits this additional technical information to the minimum required to operate the software and servers. To prevent server overloading or attacks, the servers can temporarily store data that can link to particular users or devices, including IP addresses, geographic location, or information related to the transport sessions. This information is not stored for the absolute majority of the app users, even for those who use the servers very actively. +Additional technical information can be stored on the network servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX network design limits this additional technical information to the minimum required to operate the software and servers. To prevent server overloading or attacks, the servers can temporarily store data that can link to particular users or devices, including IP addresses, geographic location, or information related to the transport sessions. This information is not stored for the absolute majority of the app users, even for those who use the servers very actively. #### SimpleX Directory +This section applies only to the experimental group directory operated by SimpleX Chat Ltd. + [SimpleX Directory](/docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the registered groups. You can connect to SimpleX Directory via [this address](https://simplex.chat/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). +#### Public groups and content channels + +You may participate in a public group and receive content from a public channel (Group). In case you send messages or comments to the Group, you grant a license: +- to all recipients: + - to share your messages with the new Group members and outside of the group, e.g. via quoting (replying), forwarding and copy-pasting your message. When your message is deleted or marked as deleted, the copies of your message will not be deleted. + - to retain a copy of your messages according to the Group settings (e.g., the Group may allow irreversible message deletion from the recipient devices for a limited period of time, or it may only allow to edit and mark messages as deleted on recipient devices). Deleting message from the recipient devices or marking message as deleted revokes the license to share the message. +- to Group owners: to share your messages with the new Group members as history of the Group. Currently, the Group history shared with the new members is limited to 100 messages. + +Group owners may use chat relays or automated bots (Chat Relays) to re-broadcast member messages to all members, for efficiency. The Chat Relays may be operated by the group owners, by preset operators or by 3rd parties. The Chat Relays have access to and will retain messages in line with Group settings, for technical functioning of the Group. Neither you nor group owners grant any content license to Chat Relay operators. + #### User Support -If you contact SimpleX Chat Ltd, any personal data you share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion) when it is possible, and avoid sharing any personal information. +The app includes support contact operated by SimpleX Chat Ltd. If you contact support, any personal data you share is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion) when it is possible, and avoid sharing any personal information. -### Information we may share +### Preset Server Operators -SimpleX Chat Ltd operates preset relay servers using third parties. While we do not have access and cannot share any user data, these third parties may access the encrypted user messages (but NOT the actual unencrypted message content or size) as it is stored or transmitted via our servers. Hosting providers can also store IP addresses and other transport information as part of their logs. +Preset server operators will not share the information on their servers with each other, other than aggregate usage statistics. -We use a third party for email services - if you ask for support via email, your and SimpleX Chat Ltd email providers may access these emails according to their privacy policies and terms. When the request is sensitive, we recommend contacting us via SimpleX Chat or using encrypted email using PGP key published at [openpgp.org](https://keys.openpgp.org/search?q=chat%40simplex.chat). +Preset server operators must not provide general access to their servers or the data on their servers to each other. -The cases when SimpleX Chat Ltd may share the data temporarily stored on the servers: +Preset server operators will provide non-administrative access to control port of preset servers to SimpleX Chat Ltd, for the purposes of removing illegal content identified in publicly accessible resources (contact and group addresses, and downloadable files). This control port access only allows deleting known links and files, and accessing aggregate server-wide statistics, but does NOT allow enumerating any information on the servers or accessing statistics related to specific users. + +### Information Preset Server Operators May Share + +The preset server operators use third parties. While they do not have access and cannot share any user data, these third parties may access the encrypted user messages (but NOT the actual unencrypted message content or size) as it is stored or transmitted via the servers. Hosting and network providers can also store IP addresses and other transport information as part of their logs. + +SimpleX Chat Ltd uses a third party for email services - if you ask for support via email, your and SimpleX Chat Ltd email providers may access these emails according to their privacy policies and terms. When the request is sensitive, please contact us via SimpleX Chat apps or using encrypted email using PGP key published at [openpgp.org](https://keys.openpgp.org/search?q=chat%40simplex.chat). + +The cases when the preset server operators may share the data temporarily stored on the servers: - To meet any applicable law, or enforceable governmental request or court order. - To enforce applicable terms, including investigation of potential violations. - To detect, prevent, or otherwise address fraud, security, or technical issues. -- To protect against harm to the rights, property, or safety of software users, SimpleX Chat Ltd, or the public as required or permitted by law. +- To protect against harm to the rights, property, or safety of software users, operators of preset servers, or the public as required or permitted by law. -At the time of updating this document, we have never provided or have been requested the access to the preset relay servers or any information from the servers by any third parties. If we are ever requested to provide such access or information, we will follow the due legal process to limit any information shared with the third parties to the minimally required by law. +By the time of updating this document, the preset server operators were not served with any enforceable requests and did not provide any information from the servers to any third parties. If the preset server operators are ever requested to provide such access or information, they will follow the due legal process to limit any information shared with the third parties to the minimally required by law. -We will publish information we are legally allowed to share about such requests in the [Transparency reports](./docs/TRANSPARENCY.md). +Preset server operators will publish information they are legally allowed to share about such requests in the [Transparency reports](./docs/TRANSPARENCY.md). + +### Source code license + +As this software is fully open-source and provided under AGPLv3 license, all infrastructure owners and operators, and the developers of the client and server applications who use the SimpleX Chat source code, are required to publish any changes to this software under the same AGPLv3 license - including any modifications to the servers. + +In addition to the AGPLv3 license terms, the preset relay server operators are committed to the software users that these servers will always be compiled from the [published open-source code](https://github.com/simplex-chat/simplexmq), without any modifications. ### Updates -We will update this Privacy Policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our software applications and preset relays infrastructure confirms your acceptance of our updated Privacy Policy. +This Privacy Policy applies to SimpleX Chat Ltd and all other preset server operators you use in the app. -Please also read our Conditions of Use of Software and Infrastructure below. +This Privacy Policy may be updated as needed so that it is current, accurate, and as clear as possible. When it is updated, you will have to review and accept the changed policy within 30 days of such changes to continue using preset relay servers. Even if you fail to accept the changed policy, your continued use of SimpleX Chat software applications and preset relay servers confirms your acceptance of the updated Privacy Policy. -If you have questions about our Privacy Policy please contact us via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). +Please also read The Conditions of Use of Software and Infrastructure below. + +If you have questions about this Privacy Policy please contact SimpleX Chat Ltd via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). ## Conditions of Use of Software and Infrastructure -You accept the Conditions of Use of Software and Infrastructure ("Conditions") by installing or using any of our software or using any of our server infrastructure (collectively referred to as "Applications"), whether preset in the software or not. +You accept the Conditions of Use of Software and Infrastructure ("Conditions") by installing or using any of SimpleX Chat software or using any of server infrastructure (collectively referred to as "Applications") operated by the Preset Server Operators, including SimpleX Chat Ltd, whether these servers are preset in the software or not. -**Minimal age**. You must be at least 13 years old to use our Applications. The minimum age to use our Applications without parental approval may be higher in your country. +**Minimal age**. You must be at least 13 years old to use SimpleX Chat Applications. The minimum age to use SimpleX Applications without parental approval may be higher in your country. -**Infrastructure**. Our Infrastructure includes preset messaging and file relay servers, and iOS push notification servers provided by SimpleX Chat Ltd for public use. Our infrastructure does not have any modifications from the [published open-source code](https://github.com/simplex-chat/simplexmq) available under AGPLv3 license. Any infrastructure provider, whether commercial or not, is required by the Affero clause (named after Affero Inc. company that pioneered the community-based Q&A sites in early 2000s) to publish any modifications under the same license. The statements in relation to Infrastructure and relay servers anywhere in this document assume no modifications to the published code, even in the cases when it is not explicitly stated. +**Infrastructure**. Infrastructure of the preset server operators includes messaging and file relay servers. SimpleX Chat Ltd also provides iOS push notification servers for public use. This infrastructure does not have any modifications from the [published open-source code](https://github.com/simplex-chat/simplexmq) available under AGPLv3 license. Any infrastructure provider, whether commercial or not, is required by the Affero clause (named after Affero Inc. company that pioneered the community-based Q&A sites in early 2000s) to publish any modifications under the same license. The statements in relation to Infrastructure and relay servers anywhere in this document assume no modifications to the published code, even in the cases when it is not explicitly stated. -**Client applications**. Our client application Software (referred to as "app" or "apps") also has no modifications compared with published open-source code, and any developers of the alternative client apps based on our code are required to publish any modifications under the same AGPLv3 license. Client applications should not include any tracking or analytics code, and do not share any information with SimpleX Chat Ltd or any other third parties. If you ever discover any tracking or analytics code, please report it to us, so we can remove it. +**Client applications**. SimpleX Chat client application Software (referred to as "app" or "apps") also has no modifications compared with published open-source code, and any developers of the alternative client apps based on SimpleX Chat code are required to publish any modifications under the same AGPLv3 license. Client applications should not include any tracking or analytics code, and do not share any tracking information with SimpleX Chat Ltd, preset server operators or any other third parties. If you ever discover any tracking or analytics code, please report it to SimpleX Chat Ltd, so it can be removed. -**Accessing the infrastructure**. For the efficiency of the network access, the client Software by default accesses all queues your app creates on any relay server within one user profile via the same network (TCP/IP) connection. At the cost of additional traffic this configuration can be changed to use different transport session for each connection. Relay servers do not collect information about which queues were created or accessed via the same connection, so the relay servers cannot establish which queues belong to the same user profile. Whoever might observe your network traffic would know which relay servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common, even inside TLS encryption layer. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks. +**Accessing the infrastructure**. For the efficiency of the network access, the client Software by default accesses all queues your app creates on any relay server within one user profile via the same network (TCP/IP) connection. At the cost of additional traffic this configuration can be changed to use different transport session for each connection. Relay servers do not collect information about which queues were created or accessed via the same connection, so the relay servers cannot establish which queues belong to the same user profile. Whoever might observe your network traffic would know which relay servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common, even inside TLS encryption layer. Please refer to the [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about the privacy model and known security and privacy risks. -**Privacy of user data**. Servers do not retain any data we transmit for any longer than necessary to deliver the messages between apps. SimpleX Chat Ltd collects aggregate statistics across all its servers, as supported by published code and can be enabled by any infrastructure provider, but not any statistics per-user, or per geographic location, or per IP address, or per transport session. We do not have information about how many people use SimpleX Chat applications, we only know an approximate number of app installations and the aggregate traffic through the preset servers. In any case, we do not and will not sell or in any way monetize user data. Our future business model assumes charging for some optional Software features instead, in a transparent and fair way. +**Privacy of user data**. Servers do not retain any data you transmit for any longer than necessary to deliver the messages between apps. Preset server operators collect aggregate statistics across all their servers, as supported by published code and can be enabled by any infrastructure operator, but not any statistics per-user, or per geographic location, or per IP address, or per transport session. SimpleX Chat Ltd does not have information about how many people use SimpleX Chat applications, it only knows an approximate number of app installations and the aggregate traffic through the preset servers. In any case, preset server operators do not and will not sell or in any way monetize user data. The future business model assumes charging for some optional Software features instead, in a transparent and fair way. -**Operating our Infrastructure**. For the purpose of using our Software, if you continue using preset servers, you agree that your end-to-end encrypted messages are transferred via the preset servers in any countries where we have or use facilities and service providers or partners. The information about geographic location of the servers will be made available in the apps in the near future. +**Operating Infrastructure**. For the purpose of using SimpleX Chat Software, if you continue using preset servers, you agree that your end-to-end encrypted messages are transferred via the preset servers in any countries where preset server operators have or use facilities and service providers or partners. The information about geographic location and hosting providers of the preset messaging servers is available on server pages. -**Software**. You agree to downloading and installing updates to our Applications when they are available; they would only be automatic if you configure your devices in this way. +**Software**. You agree to downloading and installing updates to SimpleX Chat Applications when they are available; they would only be automatic if you configure your devices in this way. -**Traffic and device costs**. You are solely responsible for the traffic and device costs that you incur while using our Applications, and any associated taxes. +**Traffic and device costs**. You are solely responsible for the traffic and device costs that you incur while using SimpleX Chat Applications, and any associated taxes. -**Legal usage**. You agree to use our Applications only for legal purposes. You will not use (or assist others in using) our Applications in ways that: 1) violate or infringe the rights of Software users, SimpleX Chat Ltd, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal communications, e.g. spam. While we cannot access content or identify messages or groups, in some cases the links to the illegal communications available via our Applications can be shared publicly on social media or websites. We reserve the right to remove such links from the preset servers and disrupt the conversations that send illegal content via our servers, whether they were reported by the users or discovered by our team. +**Legal usage**. You agree to use SimpleX Chat Applications only for legal purposes. You will not use (or assist others in using) the Applications in ways that: 1) violate or infringe the rights of Software users, SimpleX Chat Ltd, other preset server operators, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal communications, e.g. spam. While server operators cannot access content or identify messages or groups, in some cases the links to the illegal communications can be shared publicly on social media or websites. Preset server operators reserve the right to remove such links from the preset servers and disrupt the conversations that send illegal content via their servers, whether they were reported by the users or discovered by the operators themselves. -**Damage to SimpleX Chat Ltd**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit our Applications in unauthorized manners, or in ways that harm Software users, SimpleX Chat Ltd, our Infrastructure, or any other systems. For example, you must not 1) access our Infrastructure or systems without authorization, in any way other than by using the Software; 2) disrupt the integrity or performance of our Infrastructure; 3) collect information about our users in any manner; or 4) sell, rent, or charge for our Infrastructure. This does not prohibit you from providing your own Infrastructure to others, whether free or for a fee, as long as you do not violate these Conditions and AGPLv3 license, including the requirement to publish any modifications of the relay server software. +**Damage to SimpleX Chat Ltd and Preset Server Operators**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit SimpleX Chat Applications in unauthorized manners, or in ways that harm Software users, SimpleX Chat Ltd, other preset server operators, their Infrastructure, or any other systems. For example, you must not 1) access preset operators' Infrastructure or systems without authorization, in any way other than by using the Software or by using a 3rd party client applications that satisfies the requirements of the Conditions of use (see the next section); 2) disrupt the integrity or performance of preset operators' Infrastructure; 3) collect information about the users in any manner; or 4) sell, rent, or charge for preset operators' Infrastructure. This does not prohibit you from providing your own Infrastructure to others, whether free or for a fee, as long as you do not violate these Conditions and AGPLv3 license, including the requirement to publish any modifications of the relay server software. -**Keeping your data secure**. SimpleX Chat is the first communication software that aims to be 100% private by design - server software neither has the ability to access your messages, nor it has information about who you communicate with. That means that you are solely responsible for keeping your device, your user profile and any data safe and secure. If you lose your phone or remove the Software from the device, you will not be able to recover the lost data, unless you made a back up. To protect the data you need to make regular backups, as using old backups may disrupt your communication with some of the contacts. +**3rd party client applications**. You may use a 3rd party application (App) to access preset operators' Infrastructure or systems, provided that this App: +- is compatible with the protocol specifications not older than 1 year, +- provides user-to-user messaging only or enables automated chat bots sending messages requested by users (in case of bots, it must be made clear to the users that these are automated bots), +- implements the same limits, rules and restrictions as Software, +- requires that the users accept the same Conditions of use of preset operators' Infrastructure as in Software prior to providing access to this Infrastructure, +- displays the notice that it is the App for using SimpleX network, +- provides its source code under open-source license accessible to the users via the App interface. In case the App uses the source code of Software, the App's source code must be provided under AGPLv3 license, and in case it is developed without using Software code its source code must be provided under any widely recognized free open-source license, +- does NOT use the branding of SimpleX Chat Ltd without the permission, +- does NOT pretend to be Software, +- complies with these Conditions of use. + +**Keeping your data secure**. SimpleX Chat is the first communication software that aims to be 100% private by design - server software neither has the ability to access your messages, nor it has information about who you communicate with. That means that you are solely responsible for keeping your device, your user profile and any data safe and secure. If you lose your phone or remove the Software from the device, you will not be able to recover the lost data, unless you made a back up. To protect the data you need to make regular backups, as using old backups may disrupt your communication with some of the contacts. SimpleX Chat Ltd and other preset server operators are not responsible for any data loss. **Storing the messages on the device**. The messages are stored in the encrypted database on your device. Whether and how database passphrase is stored is determined by the configuration of the Software you use. The databases created prior to 2023 or in CLI (terminal) app may remain unencrypted, and it will be indicated in the app interface. In this case, if you make a backup of the data and store it unencrypted, the backup provider may be able to access the messages. Please note, that the desktop apps can be configured to store the database passphrase in the configuration file in plaintext, and unless you set the passphrase when first running the app, a random passphrase will be used and stored on the device. You can remove it from the device via the app settings. **Storing the files on the device**. The files currently sent and received in the apps by default (except CLI app) are stored on your device encrypted using unique keys, different for each file, that are stored in the database. Once the message that the file was attached to is removed, even if the copy of the encrypted file is retained, it should be impossible to recover the key allowing to decrypt the file. This local file encryption may affect app performance, and it can be disabled via the app settings. This change will only affect the new files. If you later re-enable the encryption, it will also affect only the new files. If you make a backup of the app data and store it unencrypted, the backup provider will be able to access any unencrypted files. In any case, irrespective of the storage setting, the files are always sent by all apps end-to-end encrypted. -**No Access to Emergency Services**. Our Applications do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service. +**No Access to Emergency Services**. SimpleX Chat Applications do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service. -**Third-party services**. Our Applications may allow you to access, use, or interact with our or third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services. +**Third-party services**. SimpleX Chat Applications may allow you to access, use, or interact with the websites of SimpleX Chat Ltd, preset server operators or other third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services. -**Your Rights**. You own the messages and the information you transmit through our Applications. Your recipients are able to retain the messages they receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the Software. At the same time, repudiation property of the end-to-end encryption algorithm allows you to plausibly deny having sent the message, like you can deny what you said in a private face-to-face conversation, as the recipient cannot provide any proof to the third parties, by design. +**Your Rights**. You own the messages and the information you transmit through SimpleX Applications. Your recipients are able to retain the messages they receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the Software. At the same time, repudiation property of the end-to-end encryption algorithm allows you to plausibly deny having sent the message, like you can deny what you said in a private face-to-face conversation, as the recipient cannot provide any proof to the third parties, by design. -**License**. SimpleX Chat Ltd grants you a limited, revocable, non-exclusive, and non-transferable license to use our Applications in accordance with these Conditions. The source-code of Applications is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE). +**License**. SimpleX Chat Ltd grants you a limited, revocable, non-exclusive, and non-transferable license to use SimpleX Chat Applications in accordance with these Conditions. The source-code of Applications is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE). -**SimpleX Chat Ltd Rights**. We own all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with our Applications. You may not use our copyrights, trademarks, domains, logos, and other intellectual property rights unless you have our written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat. +**SimpleX Chat Ltd Rights**. SimpleX Chat Ltd (and, where applicable, preset server operators) owns all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with the Applications. You may not use SimpleX Chat Ltd copyrights, trademarks, domains, logos, and other intellectual property rights unless you have SimpleX Chat Ltd written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat. -**Disclaimers**. YOU USE OUR APPLICATIONS AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. WE PROVIDE OUR APPLICATIONS ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX CHAT LTD DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY US IS ACCURATE, COMPLETE, OR USEFUL, THAT OUR APPLICATIONS WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT OUR APPLICATIONS WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. WE DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN OUR USERS USE OUR APPLICATIONS. WE ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF OUR USERS OR OTHER THIRD PARTIES. YOU RELEASE US, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES. +**Disclaimers**. YOU USE SIMPLEX APPLICATIONS AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. SIMPLEX CHAT LTD PROVIDES APPLICATIONS ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX CHAT LTD DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY THEM IS ACCURATE, COMPLETE, OR USEFUL, THAT THEIR APPLICATIONS WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT THEIR APPLICATIONS WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. SIMPLEX CHAT LTD AND OTHER PRESET OPERATORS DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN THE USERS USE APPLICATIONS. SIMPLEX CHAT LTD AND OTHER PRESET OPERATORS ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF THEIR USERS OR OTHER THIRD PARTIES. YOU RELEASE SIMPLEX CHAT LTD, OTHER PRESET OPERATORS, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES. -**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR OUR APPLICATIONS, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR AGGREGATE LIABILITY RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR OUR APPLICATIONS WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN OUR CONDITIONS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW. +**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR SIMPLEX APPLICATIONS, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE AGGREGATE LIABILITY OF THE SIMPLEX PARTIES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH THESE CONDITIONS, THE SIMPLEX PARTIES, OR THE APPLICATIONS WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THE CONDITIONS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW. -**Availability**. Our Applications may be disrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Applications, including certain features and the support for certain devices and platforms, at any time. +**Availability**. The Applications may be disrupted, including for maintenance, upgrades, or network or equipment failures. SimpleX Chat Ltd may discontinue some or all of their Applications, including certain features and the support for certain devices and platforms, at any time. Preset server operators may discontinue providing the servers, at any time. -**Resolving disputes**. You agree to resolve any Claim you have with us relating to or arising from our Conditions, us, or our Applications in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern our Conditions, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat Ltd and you, without regard to conflict of law provisions. +**Resolving disputes**. You agree to resolve any Claim you have with SimpleX Chat Ltd and/or preset server operators relating to or arising from these Conditions, them, or the Applications in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern these Conditions, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat Ltd (or preset server operators) and you, without regard to conflict of law provisions. -**Changes to the conditions**. SimpleX Chat Ltd may update the Conditions from time to time. Your continued use of our Applications confirms your acceptance of our updated Conditions and supersedes any prior Conditions. You will comply with all applicable export control and trade sanctions laws. Our Conditions cover the entire agreement between you and SimpleX Chat Ltd regarding our Applications. If you do not agree with our Conditions, you should stop using our Applications. +**Changes to the conditions**. SimpleX Chat Ltd may update the Conditions from time to time. The updated conditions have to be accepted within 30 days. Even if you fail to accept updated conditions, your continued use of SimpleX Chat Applications confirms your acceptance of the updated Conditions and supersedes any prior Conditions. You will comply with all applicable export control and trade sanctions laws. These Conditions cover the entire agreement between you and SimpleX Chat Ltd, and any preset server operators where applicable, regarding SimpleX Chat Applications. If you do not agree with these Conditions, you should stop using the Applications. -**Enforcing the conditions**. If we fail to enforce any of our Conditions, that does not mean we waive the right to enforce them. If any provision of the Conditions is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from our Conditions and shall not affect the enforceability of the remaining provisions. Our Applications are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject us to any regulations in another country. We reserve the right to limit our Applications in any country. If you have specific questions about these Conditions, please contact us at chat@simplex.chat. +**Enforcing the conditions**. If SimpleX Chat Ltd or preset server operators fail to enforce any of these Conditions, that does not mean they waive the right to enforce them. If any provision of the Conditions is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from the Conditions and shall not affect the enforceability of the remaining provisions. The Applications are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject SimpleX Chat Ltd to any regulations in another country. SimpleX Chat Ltd reserve the right to limit the access to the Applications in any country. Preset operators reserve the right to limit access to their servers in any country. If you have specific questions about these Conditions, please contact SimpleX Chat Ltd at chat@simplex.chat. -**Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd at any time by deleting our Applications from your devices and discontinuing use of our Infrastructure. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd. +**Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd and preset server operators at any time by deleting the Applications from your devices and discontinuing use of the Infrastructure of SimpleX Chat Ltd and preset server operators. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd and/or preset server operators. -Updated April 24, 2024 +Updated March 3, 2025 diff --git a/README.md b/README.md index 1a500187c0..554c6068d9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ # SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design! -[](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)     [](https://www.privacyguides.org/en/real-time-communication/#simplex-chat)     [](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/) +[](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)     [](https://www.privacyguides.org/en/real-time-communication/#simplex-chat)     [](https://www.whonix.org/wiki/Chat#Recommendation)     [](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/) ## Welcome to SimpleX Chat! @@ -72,7 +72,7 @@ You must: Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment. -You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) +You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FiBkJE72asZX1NUZaYFIeKRVk6oVjb-iv%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAinqu3j74AMjODLoIRR487ZW6ysip_dlpD6Zxk18SPFY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22groupLinkId%22%3A%223wAFGCLygQHR5AwynZOHlQ%3D%3D%22%7D) There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FvYCRjIflKNMGYlfTkuHe4B40qSlQ0439%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAHNdcqNbzXZhyMoSBjT2R0-Eb1EPaLyUg3KZjn-kmM1w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22PD20tcXjw7IpkkMCfR6HLA%3D%3D%22%7D) for developers who build on SimpleX platform: @@ -83,7 +83,7 @@ There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=s There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users: -[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FkIEl7OQzcp-J6aDmjdlQbRJwqkcZE7XR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAR16PCu02MobRmKAsjzhDWMZcWP9hS8l5AUZi-Gs8z18%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22puYPMCQt11yPUvgmI5jCiw%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FaJ8O1O8A8GbeoaHTo_V8dcefaCl7ouPb%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA034qWTA3sWcTsi6aWhNf9BA34vKVCFaEBdP2R66z6Ao%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22wiZ1v_wNjLPlT-nCSB-bRA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FvIHQDxTor53nwnWWTy5cHNwQQAdWN5Hw%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAPdgK1eBnETmgiqEQufbUkydKBJafoRx4iRrtrC2NAGc%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%221FyUryBPza-1ZFFE80Ekbg%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FXZyt3hJmWsycpN7Dqve_wbrAqb6myk1R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMFVIoytozTEa_QXOgoZFq_oe0IwZBYKvW50trSFXzXo%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xz05ngjA3pNIxLZ32a8Vxg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F0weR-ZgDUl7ruOtI_8TZwEsnJP6UiImA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAq4PSThO9Fvb5ydF48wB0yNbpzCbuQJCW3vZ9BGUfcxk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22e-iceLA0SctC62eARgYDWg%3D%3D%22%7D) (Italian-speaking). +[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FmfiivxDKWFuowXrQOp11jsY8TuP__rBL%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAiz3pKNwvKudckFYMUfgoT0s96B0jfZ7ALHAu7rtE9HQ%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jZeJpXGrRXQJU_-MSJ_v2A%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FJ5ES83pJimY2BRklS8fvy_iQwIU37xra%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA0F0STP6UqN_12_k2cjjTrIjFgBGeWhOAmbY1qlk3pnM%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22VmUU0fqmYdCRmVCyvStvHA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FxCHBE_6PBRMqNEpm4UQDHXb9cz-mN7dd%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAetqlcM7zTCRw-iatnwCrvpJSto7lq5Yv6AsBMWv7GSM%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22foO5Xw4hhjOa_x7zET7otw%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FVXQTB0J2lLjYkgjWByhl6-1qmb5fgZHh%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAI6JaEWezfSwvcoTEkk6au-gkjrXR2ew2OqZYMYBvayk%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22ORH9OEe8Duissh-hslfeVg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FqpHu0psOUdYfc11yQCzSyq5JhijrBzZT%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEACZ_7fbwlM45wl6cGif8cY47oPQ_AMdP0ATqOYLA6zHY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%229uRQRTir3ealdcSfB0zsrw%3D%3D%22%7D) (Italian-speaking). You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code. @@ -110,6 +110,15 @@ After you connect, you can [verify connection security code](./blog/20230103-sim Read about the app features and settings in the new [User guide](./docs/guide/README.md). +## Contribute + +We would love to have you join the development! You can help us with: + +- [share the color theme](./docs/THEMES.md) you use in Android app! +- writing a tutorial or recipes about hosting servers, chat bot automations, etc. +- contributing to SimpleX Chat knowledge-base. +- developing features - please connect to us via chat so we can help you get started. + ## Help translating SimpleX Chat Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages. @@ -141,15 +150,6 @@ Join our translators to help SimpleX grow! Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed – please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us! -## Contribute - -We would love to have you join the development! You can help us with: - -- [share the color theme](./docs/THEMES.md) you use in Android app! -- writing a tutorial or recipes about hosting servers, chat bot automations, etc. -- contributing to SimpleX Chat knowledge-base. -- developing features - please connect to us via chat so we can help you get started. - ## Please support us with your donations Huge thank you to everybody who donated to SimpleX Chat! @@ -163,13 +163,15 @@ Your donations help us raise more funds - any amount, even the price of the cup It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) (commission-free) or [OpenCollective](https://opencollective.com/simplex-chat) (~10% commission). -- Bitcoin: bc1qd74rc032ek2knhhr3yjq2ajzc5enz3h4qwnxad -- Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- BTC: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- XMR: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt - BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg -- Ethereum: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 -- USDT: - - Ethereum: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 -- Solana: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu +- ETH: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 +- USDT (Ethereum): 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 +- ZEC: t1fwjQW5gpFhDqXNhxqDWyF9j9WeKvVS5Jg +- ZEC shielded: u16rnvkflumf5uw9frngc2lymvmzgdr2mmc9unyu0l44unwfmdcpfm0axujd2w34ct3ye709azxsqge45705lpvvqu264ltzvfay55ygyq +- DOGE: D99pV4n9TrPxBPCkQGx4w4SMSa6QjRBxPf +- SOL: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu - please ask if you want to donate any other coins. Thank you, @@ -233,34 +235,28 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Mar 8, 2025. SimpleX Chat v6.3: new user experience and safety in public groups](./blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md) + +[Jan 14, 2025. SimpleX network: large groups and privacy-preserving content moderation](./blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md) + +[Dec 10, 2024. SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps](./20241210-simplex-network-v6-2-servers-by-flux-business-chats.md) + +[Oct 14, 2024. SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) + [Aug 14, 2024. SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) [Jun 4, 2024. SimpleX network: private message routing, v5.8 released with IP address protection and chat themes](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) -[Apr 26, 2024. SimpleX network: legally binding transparency, v5.7 released with better calls and messages.](./blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md) - -[Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md) - [Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) -[Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md) - [Nov 25, 2023. SimpleX Chat v5.4 released: link mobile and desktop apps via quantum resistant protocol, and much better groups](./blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md). -[Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md). - -[Jul 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md). - -[May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md). - [Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md). [Mar 1, 2023. SimpleX File Transfer Protocol – send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md). [Nov 8, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). -[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md). - [All updates](./blog) ## :zap: Quick installation of a terminal app @@ -300,25 +296,28 @@ What is already implemented: 1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses [pairwise per-queue identifiers](./docs/GLOSSARY.md#pairwise-pseudonymous-identifier) (2 addresses for each unidirectional message queue, with an optional 3rd address for push notifications on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues. 2. [End-to-end encryption](./docs/GLOSSARY.md#end-to-end-encryption) in each message queue using [NaCl cryptobox](https://nacl.cr.yp.to/box.html). This is added to allow redundancy in the future (passing each message via several servers), to avoid having the same ciphertext in different queues (that would only be visible to the attacker if TLS is compromised). The encryption keys used for this encryption are not rotated, instead we are planning to rotate the queues. Curve25519 keys are used for key negotiation. 3. [Double ratchet](./docs/GLOSSARY.md#double-ratchet-algorithm) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with [forward secrecy](./docs/GLOSSARY.md#forward-secrecy) (each message is encrypted by its own ephemeral key) and [break-in recovery](./docs/GLOSSARY.md#post-compromise-security) (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial [key agreement](./docs/GLOSSARY.md#key-agreement-protocol), initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message. -4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). -5. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. -6. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. -7. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. -8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. -9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. -10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. -11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. -12. Manual messaging queue rotations to move conversation to another SMP relay. -13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). -14. Local files encryption. +4. [Post-quantum resistant key exchange](./docs/GLOSSARY.md#post-quantum-cryptography) in double ratchet protocol *on every ratchet step*. Read more in [this post](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) and also see this [publication by Apple]( https://security.apple.com/blog/imessage-pq3/) explaining the need for post-quantum key rotation. +5. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). +6. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. +7. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. +8. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. +9. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. +10. To protect your IP address from unknown messaging relays, and for per-message transport anonymity (compared with Tor/VPN per-connection anonymity), from v6.0 all SimpleX Chat clients use private message routing by default. Read more in [this post](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing). +11. To protect your IP address from unknown file relays, when SOCKS proxy is not enabled SimpleX Chat clients ask for a confirmation before downloading the files from unknown servers. +12. To protect your IP address from known servers all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. +13. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. +14. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. +15. Manual messaging queue rotations to move conversation to another SMP relay. +16. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). +17. Local files encryption. +18. [Reproducible server builds](./docs/SERVER.md#reproduce-builds). We plan to add: -1. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. -2. Post-quantum resistant key exchange in double ratchet protocol. -3. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). -4. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. -5. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +1. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). +2. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. +3. Reproducible clients builds – this is a complex problem, but we are aiming to have it in 2025 at least partially. +4. Recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. ## For developers @@ -386,9 +385,11 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A - ✅ Improve sending videos (including encryption of locally stored videos). - ✅ Post-quantum resistant key exchange in double ratchet protocol. - ✅ Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic). +- ✅ Support multiple network operators in the app. +- 🏗 Large groups, communities and public channels. +- 🏗 Short links to connect and join groups. - 🏗 Improve stability and reduce battery usage. - 🏗 Improve experience for the new users. -- 🏗 Large groups, communities and public channels. - Privacy & security slider - a simple way to set all settings at once. - SMP queue redundancy and rotation (manual is supported). - Include optional message into connection request sent via contact address. @@ -407,7 +408,9 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A [SimpleX protocols and security model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) was reviewed, and had many breaking changes and improvements in v1.0.0. -The security audit was performed in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2.0 – see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). +The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about) – see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). + +The cryptographic review of SimpleX protocols was done in July 2024 by Trail of Bits – see [the announcement](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). SimpleX Chat is still a relatively early stage platform (the mobile apps were released in March 2022), so you may discover some bugs and missing features. We would really appreciate if you let us know anything that needs to be fixed or improved. diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index dd91d4af68..3f6998c9ec 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -15,16 +15,12 @@ class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { logger.debug("AppDelegate: didFinishLaunchingWithOptions") application.registerForRemoteNotifications() - NotificationCenter.default.addObserver(self, selector: #selector(pasteboardChanged), name: UIPasteboard.changedNotification, object: nil) removePasscodesIfReinstalled() prepareForLaunch() + deleteOldChatArchive() return true } - @objc func pasteboardChanged() { - ChatModel.shared.pasteboardHasStrings = UIPasteboard.general.hasStrings - } - func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02hhx", $0) }.joined() logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)") @@ -58,7 +54,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { try await apiVerifyToken(token: token, nonce: nonce, code: verification) m.tokenStatus = .active } catch { - if let cr = error as? ChatResponse, case .chatCmdError(_, .errorAgent(.NTF(.AUTH))) = cr { + if let cr = error as? ChatError, case .errorAgent(.NTF(.AUTH)) = cr { m.tokenStatus = .expired } logger.error("AppDelegate: didReceiveRemoteNotification: apiVerifyToken or apiIntervalNofication error: \(responseError(error))") diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json new file mode 100644 index 0000000000..8e38b499dd --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info": { + "author": "xcode", + "version": 1 + }, + "symbols": [ + { + "filename": "checkmark.2.svg", + "idiom": "universal" + } + ] +} \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg new file mode 100644 index 0000000000..577fa1db76 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg @@ -0,0 +1,227 @@ + + + checkmark.2 + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + + can be adjusted by modifying the x-location of the margin guidelines. + + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.5.0 + + + Requires Xcode 15 or greater + + + Generated from double.checkmark + + + Typeset at 100.0 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json new file mode 100644 index 0000000000..11a91cb811 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info": { + "author": "xcode", + "version": 1 + }, + "symbols": [ + { + "filename": "checkmark.wide.svg", + "idiom": "universal" + } + ] +} \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg new file mode 100644 index 0000000000..b5dfc6b3de --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg @@ -0,0 +1,218 @@ + + + checkmark.wide + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + + can be adjusted by modifying the x-location of the margin guidelines. + + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.5.0 + + + Requires Xcode 15 or greater + + + Generated from double.checkmark + + + Typeset at 100.0 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json new file mode 100644 index 0000000000..d3a15f9a33 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Flux_logo_blue_white.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Flux_logo_blue_white.png b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Flux_logo_blue_white.png new file mode 100644 index 0000000000..e1d6dda4fe Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Flux_logo_blue_white.png differ diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Contents.json new file mode 100644 index 0000000000..ad18e60448 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Flux_logo_blue.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Flux_logo_blue.png b/apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Flux_logo_blue.png new file mode 100644 index 0000000000..87f1373d75 Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Flux_logo_blue.png differ diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json new file mode 100644 index 0000000000..16686bdf80 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Flux_symbol_blue-white.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Flux_symbol_blue-white.png b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Flux_symbol_blue-white.png new file mode 100644 index 0000000000..0793b0ee85 Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Flux_symbol_blue-white.png differ diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json new file mode 100644 index 0000000000..cb29f09fe1 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "vertical_logo_x1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "vertical_logo_x2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "vertical_logo_x3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png new file mode 100644 index 0000000000..f916e43ea9 Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png differ diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png new file mode 100644 index 0000000000..bb35878f0c Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png differ diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png new file mode 100644 index 0000000000..c55f481b36 Binary files /dev/null and b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png differ diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 1f41e7c31d..2ad8d546f2 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -9,10 +9,21 @@ import SwiftUI import Intents import SimpleXChat +private enum NoticesSheet: Identifiable { + case whatsNew(updatedConditions: Bool) + + var id: String { + switch self { + case .whatsNew: return "whatsNew" + } + } +} + struct ContentView: View { @EnvironmentObject var chatModel: ChatModel @ObservedObject var alertManager = AlertManager.shared @ObservedObject var callController = CallController.shared + @ObservedObject var appSheetState = AppSheetState.shared @Environment(\.colorScheme) var colorScheme @EnvironmentObject var theme: AppTheme @EnvironmentObject var sceneDelegate: SceneDelegate @@ -29,12 +40,13 @@ struct ContentView: View { @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_NOTIFICATION_ALERT_SHOWN) private var notificationAlertShown = false - @State private var showSettings = false - @State private var showWhatsNew = false + @State private var noticesShown = false + @State private var noticesSheetItem: NoticesSheet? = nil @State private var showChooseLAMode = false @State private var showSetPasscode = false @State private var waitingForOrPassedAuth = true @State private var chatListActionSheet: ChatListActionSheet? = nil + @State private var chatListUserPickerSheet: UserPickerSheet? = nil private let callTopPadding: CGFloat = 40 @@ -62,7 +74,7 @@ struct ContentView: View { } } - @ViewBuilder func allViews() -> some View { + func allViews() -> some View { ZStack { let showCallArea = chatModel.activeCall != nil && chatModel.activeCall?.callState != .waitCapabilities && chatModel.activeCall?.callState != .invitationAccepted // contentView() has to be in a single branch, so that enabling authentication doesn't trigger re-rendering and close settings. @@ -86,7 +98,7 @@ struct ContentView: View { callView(call) } - if !showSettings, let la = chatModel.laRequest { + if chatListUserPickerSheet == nil, let la = chatModel.laRequest { LocalAuthView(authRequest: la) .onDisappear { // this flag is separate from accessAuthenticated to show initializationView while we wait for authentication @@ -109,9 +121,6 @@ struct ContentView: View { } } .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } - .sheet(isPresented: $showSettings) { - SettingsView(showSettings: $showSettings) - } .confirmationDialog("SimpleX Lock mode", isPresented: $showChooseLAMode, titleVisibility: .visible) { Button("System authentication") { initialEnableLA() } Button("Passcode entry") { showSetPasscode = true } @@ -200,7 +209,7 @@ struct ContentView: View { } } - @ViewBuilder private func activeCallInteractiveArea(_ call: Call) -> some View { + private func activeCallInteractiveArea(_ call: Call) -> some View { HStack { Text(call.contact.displayName).font(.body).foregroundColor(.white) Spacer() @@ -253,7 +262,8 @@ struct ContentView: View { private func mainView() -> some View { ZStack(alignment: .top) { - ChatListView(showSettings: $showSettings).privacySensitive(protectScreen) + ChatListView(activeUserPickerSheet: $chatListUserPickerSheet) + .redacted(reason: appSheetState.redactionReasons(protectScreen)) .onAppear { requestNtfAuthorization() // Local Authentication notice is to be shown on next start after onboarding is complete @@ -262,17 +272,31 @@ struct ContentView: View { alertManager.showAlert(laNoticeAlert()) } else if !chatModel.showCallView && CallController.shared.activeCallInvitation == nil { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - if !showWhatsNew { - showWhatsNew = shouldShowWhatsNew() + if !noticesShown { + let showWhatsNew = shouldShowWhatsNew() + let showUpdatedConditions = chatModel.conditions.conditionsAction?.showNotice ?? false + noticesShown = showWhatsNew || showUpdatedConditions + if showWhatsNew || showUpdatedConditions { + noticesSheetItem = .whatsNew(updatedConditions: showUpdatedConditions) + } } } } prefShowLANotice = true connectViaUrl() + showReRegisterTokenAlert() } .onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() } - .sheet(isPresented: $showWhatsNew) { - WhatsNewView() + .onChange(of: chatModel.reRegisterTknStatus) { _ in showReRegisterTokenAlert() } + .sheet(item: $noticesSheetItem) { item in + switch item { + case let .whatsNew(updatedConditions): + WhatsNewView(updatedConditions: updatedConditions) + .modifier(ThemedBackground()) + .if(updatedConditions) { v in + v.task { await setConditionsNotified_() } + } + } } if chatModel.setDeliveryReceipts { SetDeliveryReceiptsView() @@ -282,6 +306,21 @@ struct ContentView: View { .onContinueUserActivity("INStartCallIntent", perform: processUserActivity) .onContinueUserActivity("INStartAudioCallIntent", perform: processUserActivity) .onContinueUserActivity("INStartVideoCallIntent", perform: processUserActivity) + .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in + if let url = userActivity.webpageURL { + logger.debug("onContinueUserActivity.NSUserActivityTypeBrowsingWeb: \(url)") + chatModel.appOpenUrl = url + } + } + } + + private func setConditionsNotified_() async { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + try await setConditionsNotified(conditionsId: conditionsId) + } catch let error { + logger.error("setConditionsNotified error: \(responseError(error))") + } } private func processUserActivity(_ activity: NSUserActivity) { @@ -300,9 +339,18 @@ struct ContentView: View { if let contactId = contacts?.first?.personHandle?.value, let chat = chatModel.getChat(contactId), case let .direct(contact) = chat.chatInfo { - logger.debug("callToRecentContact: schedule call") - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - CallController.shared.startCall(contact, mediaType) + let activeCall = chatModel.activeCall + // This line works when a user clicks on a video button in CallKit UI while in call. + // The app tries to make another call to the same contact and overwite activeCall instance making its state broken + if let activeCall, contactId == activeCall.contact.id, mediaType == .video, !activeCall.hasVideo { + Task { + await chatModel.callCommand.processCommand(.media(source: .camera, enable: true)) + } + } else if activeCall == nil { + logger.debug("callToRecentContact: schedule call") + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + CallController.shared.startCall(contact, mediaType) + } } } } @@ -395,12 +443,12 @@ struct ContentView: View { } func connectViaUrl() { - dismissAllSheets() { - let m = ChatModel.shared - if let url = m.appOpenUrl { - m.appOpenUrl = nil + let m = ChatModel.shared + if let url = m.appOpenUrl { + m.appOpenUrl = nil + dismissAllSheets() { var path = url.path - if (path == "/contact" || path == "/invitation") { + if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") { path.removeFirst() let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") planAndConnect( @@ -417,6 +465,21 @@ struct ContentView: View { } } + func showReRegisterTokenAlert() { + dismissAllSheets() { + let m = ChatModel.shared + if let errorTknStatus = m.reRegisterTknStatus, let token = chatModel.deviceToken { + chatModel.reRegisterTknStatus = nil + AlertManager.shared.showAlert(Alert( + title: Text("Notifications error"), + message: Text(tokenStatusInfo(errorTknStatus, register: true)), + primaryButton: .default(Text("Register")) { reRegisterToken(token: token) }, + secondaryButton: .cancel() + )) + } + } + } + private func showPlanAndConnectAlert(_ alert: PlanAndConnectAlert) { AlertManager.shared.showAlert(planAndConnectAlert(alert, dismiss: false)) } diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift new file mode 100644 index 0000000000..3bf4cb7b56 --- /dev/null +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -0,0 +1,2281 @@ +// +// APITypes.swift +// SimpleX +// +// Created by EP on 01/05/2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SimpleXChat +import SwiftUI + +// some constructors are used in SEChatCommand or NSEChatCommand types as well - they must be syncronised +enum ChatCommand: ChatCmdProtocol { + case showActiveUser + case createActiveUser(profile: Profile?, pastTimestamp: Bool) + case listUsers + case apiSetActiveUser(userId: Int64, viewPwd: String?) + case setAllContactReceipts(enable: Bool) + case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) + case apiSetUserGroupReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) + case apiHideUser(userId: Int64, viewPwd: String) + case apiUnhideUser(userId: Int64, viewPwd: String) + case apiMuteUser(userId: Int64) + case apiUnmuteUser(userId: Int64) + case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?) + case startChat(mainApp: Bool, enableSndFiles: Bool) + case checkChatRunning + case apiStopChat + case apiActivateChat(restoreChat: Bool) + case apiSuspendChat(timeoutMicroseconds: Int) + case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) + case apiSetEncryptLocalFiles(enable: Bool) + case apiExportArchive(config: ArchiveConfig) + case apiImportArchive(config: ArchiveConfig) + case apiDeleteStorage + case apiStorageEncryption(config: DBEncryptionConfig) + case testStorageEncryption(key: String) + case apiSaveSettings(settings: AppSettings) + case apiGetSettings(settings: AppSettings) + case apiGetChatTags(userId: Int64) + case apiGetChats(userId: Int64) + case apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String) + case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) + case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + case apiCreateChatTag(tag: ChatTagData) + case apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64]) + case apiDeleteChatTag(tagId: Int64) + case apiUpdateChatTag(tagId: Int64, tagData: ChatTagData) + case apiReorderChatTags(tagIds: [Int64]) + case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) + case apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String) + case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool) + case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) + case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) + case apiArchiveReceivedReports(groupId: Int64) + case apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode) + case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) + case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction) + case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) + case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) + case apiGetNtfToken + case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) + case apiVerifyToken(token: DeviceToken, nonce: String, code: String) + case apiCheckToken(token: DeviceToken) + case apiDeleteToken(token: DeviceToken) + case apiGetNtfConns(nonce: String, encNtfInfo: String) + case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) + case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) + case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) + case apiJoinGroup(groupId: Int64) + case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole) + case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool) + case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool) + case apiLeaveGroup(groupId: Int64) + case apiListMembers(groupId: Int64) + case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) + case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole, short: Bool) + case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) + case apiDeleteGroupLink(groupId: Int64) + case apiGetGroupLink(groupId: Int64) + case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) + case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) + case apiTestProtoServer(userId: Int64, server: String) + case apiGetServerOperators + case apiSetServerOperators(operators: [ServerOperator]) + case apiGetUserServers(userId: Int64) + case apiSetUserServers(userId: Int64, userServers: [UserOperatorServers]) + case apiValidateServers(userId: Int64, userServers: [UserOperatorServers]) + case apiGetUsageConditions + case apiSetConditionsNotified(conditionsId: Int64) + case apiAcceptConditions(conditionsId: Int64, operatorIds: [Int64]) + case apiSetChatItemTTL(userId: Int64, seconds: Int64) + case apiGetChatItemTTL(userId: Int64) + case apiSetChatTTL(userId: Int64, type: ChatType, id: Int64, seconds: Int64?) + case apiSetNetworkConfig(networkConfig: NetCfg) + case apiGetNetworkConfig + case apiSetNetworkInfo(networkInfo: UserNetworkInfo) + case reconnectAllServers + case reconnectServer(userId: Int64, smpServer: String) + case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) + case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings) + case apiContactInfo(contactId: Int64) + case apiGroupMemberInfo(groupId: Int64, groupMemberId: Int64) + case apiContactQueueInfo(contactId: Int64) + case apiGroupMemberQueueInfo(groupId: Int64, groupMemberId: Int64) + case apiSwitchContact(contactId: Int64) + case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64) + case apiAbortSwitchContact(contactId: Int64) + case apiAbortSwitchGroupMember(groupId: Int64, groupMemberId: Int64) + case apiSyncContactRatchet(contactId: Int64, force: Bool) + case apiSyncGroupMemberRatchet(groupId: Int64, groupMemberId: Int64, force: Bool) + case apiGetContactCode(contactId: Int64) + case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) + case apiVerifyContact(contactId: Int64, connectionCode: String?) + case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) + case apiAddContact(userId: Int64, short: Bool, incognito: Bool) + case apiSetConnectionIncognito(connId: Int64, incognito: Bool) + case apiChangeConnectionUser(connId: Int64, userId: Int64) + case apiConnectPlan(userId: Int64, connLink: String) + case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink) + case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) + case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) + case apiClearChat(type: ChatType, id: Int64) + case apiListContacts(userId: Int64) + case apiUpdateProfile(userId: Int64, profile: Profile) + case apiSetContactPrefs(contactId: Int64, preferences: Preferences) + case apiSetContactAlias(contactId: Int64, localAlias: String) + case apiSetGroupAlias(groupId: Int64, localAlias: String) + case apiSetConnectionAlias(connId: Int64, localAlias: String) + case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) + case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?) + case apiCreateMyAddress(userId: Int64, short: Bool) + case apiDeleteMyAddress(userId: Int64) + case apiShowMyAddress(userId: Int64) + case apiSetProfileAddress(userId: Int64, on: Bool) + case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) + case apiAcceptContact(incognito: Bool, contactReqId: Int64) + case apiRejectContact(contactReqId: Int64) + // WebRTC calls + case apiSendCallInvitation(contact: Contact, callType: CallType) + case apiRejectCall(contact: Contact) + case apiSendCallOffer(contact: Contact, callOffer: WebRTCCallOffer) + case apiSendCallAnswer(contact: Contact, answer: WebRTCSession) + case apiSendCallExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo) + case apiEndCall(contact: Contact) + case apiGetCallInvitations + case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) + // WebRTC calls / + case apiGetNetworkStatuses + case apiChatRead(type: ChatType, id: Int64) + case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) + case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) + case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) + case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) + case cancelFile(fileId: Int64) + // remote desktop commands + case setLocalDeviceName(displayName: String) + case connectRemoteCtrl(xrcpInvitation: String) + case findKnownRemoteCtrl + case confirmRemoteCtrl(remoteCtrlId: Int64) + case verifyRemoteCtrlSession(sessionCode: String) + case listRemoteCtrls + case stopRemoteCtrl + case deleteRemoteCtrl(remoteCtrlId: Int64) + case apiUploadStandaloneFile(userId: Int64, file: CryptoFile) + case apiDownloadStandaloneFile(userId: Int64, url: String, file: CryptoFile) + case apiStandaloneFileInfo(url: String) + // misc + case showVersion + case getAgentSubsTotal(userId: Int64) + case getAgentServersSummary(userId: Int64) + case resetAgentServersStats + case string(String) + + var cmdString: String { + get { + switch self { + case .showActiveUser: return "/u" + case let .createActiveUser(profile, pastTimestamp): + let user = NewUser(profile: profile, pastTimestamp: pastTimestamp) + return "/_create user \(encodeJSON(user))" + case .listUsers: return "/users" + case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))" + case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))" + case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings): + let umrs = userMsgReceiptSettings + return "/_set receipts contacts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" + case let .apiSetUserGroupReceipts(userId, userMsgReceiptSettings): + let umrs = userMsgReceiptSettings + return "/_set receipts groups \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" + case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))" + case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))" + case let .apiMuteUser(userId): return "/_mute user \(userId)" + case let .apiUnmuteUser(userId): return "/_unmute user \(userId)" + case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" + case .checkChatRunning: return "/_check running" + case .apiStopChat: return "/_stop" + case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" + case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" + case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" + case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))" + case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" + case .apiDeleteStorage: return "/_db delete" + case let .apiStorageEncryption(cfg): return "/_db encryption \(encodeJSON(cfg))" + case let .testStorageEncryption(key): return "/db test key \(key)" + case let .apiSaveSettings(settings): return "/_save app settings \(encodeJSON(settings))" + case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))" + case let .apiGetChatTags(userId): return "/_get tags \(userId)" + case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" + case let .apiGetChat(chatId, pagination, search): return "/_get chat \(chatId) \(pagination.cmdString)" + + (search == "" ? "" : " search=\(search)") + case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" + case let .apiSendMessages(type, id, live, ttl, composedMessages): + let msgs = encodeJSON(composedMessages) + let ttlStr = ttl != nil ? "\(ttl!)" : "default" + return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + case let .apiCreateChatTag(tag): return "/_create tag \(encodeJSON(tag))" + case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id)) \(tagIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiDeleteChatTag(tagId): return "/_delete tag \(tagId)" + case let .apiUpdateChatTag(tagId, tagData): return "/_update tag \(tagId) \(encodeJSON(tagData))" + case let .apiReorderChatTags(tagIds): return "/_reorder tags \(tagIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiCreateChatItems(noteFolderId, composedMessages): + let msgs = encodeJSON(composedMessages) + return "/_create *\(noteFolderId) json \(msgs)" + case let .apiReportMessage(groupId, chatItemId, reportReason, reportText): + return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)" + case let .apiUpdateChatItem(type, id, itemId, um, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(um.cmdString)" + case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" + case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiArchiveReceivedReports(groupId): return "/_archive reports #\(groupId)" + case let .apiDeleteReceivedReports(groupId, itemIds, mode): return "/_delete reports #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" + case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" + case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))" + case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): + let ttlStr = ttl != nil ? "\(ttl!)" : "default" + return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" + case .apiGetNtfToken: return "/_ntf get " + case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" + case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" + case let .apiCheckToken(token): return "/_ntf check \(token.cmdString)" + case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" + case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" + case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))" + case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))" + case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)" + case let .apiJoinGroup(groupId): return "/_join #\(groupId)" + case let .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)" + case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))" + case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))" + case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" + case let .apiListMembers(groupId): return "/_members #\(groupId)" + case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" + case let .apiCreateGroupLink(groupId, memberRole, short): return "/_create link #\(groupId) \(memberRole) short=\(onOff(short))" + case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" + case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" + case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" + case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" + case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" + case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" + case .apiGetServerOperators: return "/_operators" + case let .apiSetServerOperators(operators): return "/_operators \(encodeJSON(operators))" + case let .apiGetUserServers(userId): return "/_servers \(userId)" + case let .apiSetUserServers(userId, userServers): return "/_servers \(userId) \(encodeJSON(userServers))" + case let .apiValidateServers(userId, userServers): return "/_validate_servers \(userId) \(encodeJSON(userServers))" + case .apiGetUsageConditions: return "/_conditions" + case let .apiSetConditionsNotified(conditionsId): return "/_conditions_notified \(conditionsId)" + case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))" + case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" + case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" + case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id)) \(chatItemTTLStr(seconds: seconds))" + case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" + case .apiGetNetworkConfig: return "/network" + case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))" + case .reconnectAllServers: return "/reconnect" + case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)" + case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))" + case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))" + case let .apiContactInfo(contactId): return "/_info @\(contactId)" + case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)" + case let .apiContactQueueInfo(contactId): return "/_queue info @\(contactId)" + case let .apiGroupMemberQueueInfo(groupId, groupMemberId): return "/_queue info #\(groupId) \(groupMemberId)" + case let .apiSwitchContact(contactId): return "/_switch @\(contactId)" + case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)" + case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)" + case let .apiAbortSwitchGroupMember(groupId, groupMemberId): return "/_abort switch #\(groupId) \(groupMemberId)" + case let .apiSyncContactRatchet(contactId, force): if force { + return "/_sync @\(contactId) force=on" + } else { + return "/_sync @\(contactId)" + } + case let .apiSyncGroupMemberRatchet(groupId, groupMemberId, force): if force { + return "/_sync #\(groupId) \(groupMemberId) force=on" + } else { + return "/_sync #\(groupId) \(groupMemberId)" + } + case let .apiGetContactCode(contactId): return "/_get code @\(contactId)" + case let .apiGetGroupMemberCode(groupId, groupMemberId): return "/_get code #\(groupId) \(groupMemberId)" + case let .apiVerifyContact(contactId, .some(connectionCode)): return "/_verify code @\(contactId) \(connectionCode)" + case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)" + case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)" + case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" + case let .apiAddContact(userId, short, incognito): return "/_connect \(userId) short=\(onOff(short)) incognito=\(onOff(incognito))" + case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" + case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" + case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)" + case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")" + case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" + case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)" + case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" + case let .apiListContacts(userId): return "/_contacts \(userId)" + case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" + case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))" + case let .apiSetContactAlias(contactId, localAlias): return "/_set alias @\(contactId) \(localAlias.trimmingCharacters(in: .whitespaces))" + case let .apiSetGroupAlias(groupId, localAlias): return "/_set alias #\(groupId) \(localAlias.trimmingCharacters(in: .whitespaces))" + case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" + case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")" + case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")" + case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" + case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" + case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" + case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" + case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" + case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" + case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))" + case let .apiRejectCall(contact): return "/_call reject @\(contact.apiId)" + case let .apiSendCallOffer(contact, callOffer): return "/_call offer @\(contact.apiId) \(encodeJSON(callOffer))" + case let .apiSendCallAnswer(contact, answer): return "/_call answer @\(contact.apiId) \(encodeJSON(answer))" + case let .apiSendCallExtraInfo(contact, extraInfo): return "/_call extra @\(contact.apiId) \(encodeJSON(extraInfo))" + case let .apiEndCall(contact): return "/_call end @\(contact.apiId)" + case .apiGetCallInvitations: return "/_call get" + case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" + case .apiGetNetworkStatuses: return "/_network_statuses" + case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))" + case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" + case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" + case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" + case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" + case let .cancelFile(fileId): return "/fcancel \(fileId)" + case let .setLocalDeviceName(displayName): return "/set device name \(displayName)" + case let .connectRemoteCtrl(xrcpInv): return "/connect remote ctrl \(xrcpInv)" + case .findKnownRemoteCtrl: return "/find remote ctrl" + case let .confirmRemoteCtrl(rcId): return "/confirm remote ctrl \(rcId)" + case let .verifyRemoteCtrlSession(sessCode): return "/verify remote ctrl \(sessCode)" + case .listRemoteCtrls: return "/list remote ctrls" + case .stopRemoteCtrl: return "/stop remote ctrl" + case let .deleteRemoteCtrl(rcId): return "/delete remote ctrl \(rcId)" + case let .apiUploadStandaloneFile(userId, file): return "/_upload \(userId) \(file.filePath)" + case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" + case let .apiStandaloneFileInfo(link): return "/_download info \(link)" + case .showVersion: return "/version" + case let .getAgentSubsTotal(userId): return "/get subs total \(userId)" + case let .getAgentServersSummary(userId): return "/get servers summary \(userId)" + case .resetAgentServersStats: return "/reset servers stats" + case let .string(str): return str + } + } + } + + var cmdType: String { + get { + switch self { + case .showActiveUser: return "showActiveUser" + case .createActiveUser: return "createActiveUser" + case .listUsers: return "listUsers" + case .apiSetActiveUser: return "apiSetActiveUser" + case .setAllContactReceipts: return "setAllContactReceipts" + case .apiSetUserContactReceipts: return "apiSetUserContactReceipts" + case .apiSetUserGroupReceipts: return "apiSetUserGroupReceipts" + case .apiHideUser: return "apiHideUser" + case .apiUnhideUser: return "apiUnhideUser" + case .apiMuteUser: return "apiMuteUser" + case .apiUnmuteUser: return "apiUnmuteUser" + case .apiDeleteUser: return "apiDeleteUser" + case .startChat: return "startChat" + case .checkChatRunning: return "checkChatRunning" + case .apiStopChat: return "apiStopChat" + case .apiActivateChat: return "apiActivateChat" + case .apiSuspendChat: return "apiSuspendChat" + case .apiSetAppFilePaths: return "apiSetAppFilePaths" + case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles" + case .apiExportArchive: return "apiExportArchive" + case .apiImportArchive: return "apiImportArchive" + case .apiDeleteStorage: return "apiDeleteStorage" + case .apiStorageEncryption: return "apiStorageEncryption" + case .testStorageEncryption: return "testStorageEncryption" + case .apiSaveSettings: return "apiSaveSettings" + case .apiGetSettings: return "apiGetSettings" + case .apiGetChatTags: return "apiGetChatTags" + case .apiGetChats: return "apiGetChats" + case .apiGetChat: return "apiGetChat" + case .apiGetChatItemInfo: return "apiGetChatItemInfo" + case .apiSendMessages: return "apiSendMessages" + case .apiCreateChatTag: return "apiCreateChatTag" + case .apiSetChatTags: return "apiSetChatTags" + case .apiDeleteChatTag: return "apiDeleteChatTag" + case .apiUpdateChatTag: return "apiUpdateChatTag" + case .apiReorderChatTags: return "apiReorderChatTags" + case .apiCreateChatItems: return "apiCreateChatItems" + case .apiReportMessage: return "apiReportMessage" + case .apiUpdateChatItem: return "apiUpdateChatItem" + case .apiDeleteChatItem: return "apiDeleteChatItem" + case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" + case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" + case .apiArchiveReceivedReports: return "apiArchiveReceivedReports" + case .apiDeleteReceivedReports: return "apiDeleteReceivedReports" + case .apiChatItemReaction: return "apiChatItemReaction" + case .apiGetReactionMembers: return "apiGetReactionMembers" + case .apiPlanForwardChatItems: return "apiPlanForwardChatItems" + case .apiForwardChatItems: return "apiForwardChatItems" + case .apiGetNtfToken: return "apiGetNtfToken" + case .apiRegisterToken: return "apiRegisterToken" + case .apiVerifyToken: return "apiVerifyToken" + case .apiCheckToken: return "apiCheckToken" + case .apiDeleteToken: return "apiDeleteToken" + case .apiGetNtfConns: return "apiGetNtfConns" + case .apiGetConnNtfMessages: return "apiGetConnNtfMessages" + case .apiNewGroup: return "apiNewGroup" + case .apiAddMember: return "apiAddMember" + case .apiJoinGroup: return "apiJoinGroup" + case .apiMembersRole: return "apiMembersRole" + case .apiBlockMembersForAll: return "apiBlockMembersForAll" + case .apiRemoveMembers: return "apiRemoveMembers" + case .apiLeaveGroup: return "apiLeaveGroup" + case .apiListMembers: return "apiListMembers" + case .apiUpdateGroupProfile: return "apiUpdateGroupProfile" + case .apiCreateGroupLink: return "apiCreateGroupLink" + case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" + case .apiDeleteGroupLink: return "apiDeleteGroupLink" + case .apiGetGroupLink: return "apiGetGroupLink" + case .apiCreateMemberContact: return "apiCreateMemberContact" + case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" + case .apiTestProtoServer: return "apiTestProtoServer" + case .apiGetServerOperators: return "apiGetServerOperators" + case .apiSetServerOperators: return "apiSetServerOperators" + case .apiGetUserServers: return "apiGetUserServers" + case .apiSetUserServers: return "apiSetUserServers" + case .apiValidateServers: return "apiValidateServers" + case .apiGetUsageConditions: return "apiGetUsageConditions" + case .apiSetConditionsNotified: return "apiSetConditionsNotified" + case .apiAcceptConditions: return "apiAcceptConditions" + case .apiSetChatItemTTL: return "apiSetChatItemTTL" + case .apiGetChatItemTTL: return "apiGetChatItemTTL" + case .apiSetChatTTL: return "apiSetChatTTL" + case .apiSetNetworkConfig: return "apiSetNetworkConfig" + case .apiGetNetworkConfig: return "apiGetNetworkConfig" + case .apiSetNetworkInfo: return "apiSetNetworkInfo" + case .reconnectAllServers: return "reconnectAllServers" + case .reconnectServer: return "reconnectServer" + case .apiSetChatSettings: return "apiSetChatSettings" + case .apiSetMemberSettings: return "apiSetMemberSettings" + case .apiContactInfo: return "apiContactInfo" + case .apiGroupMemberInfo: return "apiGroupMemberInfo" + case .apiContactQueueInfo: return "apiContactQueueInfo" + case .apiGroupMemberQueueInfo: return "apiGroupMemberQueueInfo" + case .apiSwitchContact: return "apiSwitchContact" + case .apiSwitchGroupMember: return "apiSwitchGroupMember" + case .apiAbortSwitchContact: return "apiAbortSwitchContact" + case .apiAbortSwitchGroupMember: return "apiAbortSwitchGroupMember" + case .apiSyncContactRatchet: return "apiSyncContactRatchet" + case .apiSyncGroupMemberRatchet: return "apiSyncGroupMemberRatchet" + case .apiGetContactCode: return "apiGetContactCode" + case .apiGetGroupMemberCode: return "apiGetGroupMemberCode" + case .apiVerifyContact: return "apiVerifyContact" + case .apiVerifyGroupMember: return "apiVerifyGroupMember" + case .apiAddContact: return "apiAddContact" + case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" + case .apiChangeConnectionUser: return "apiChangeConnectionUser" + case .apiConnectPlan: return "apiConnectPlan" + case .apiConnect: return "apiConnect" + case .apiDeleteChat: return "apiDeleteChat" + case .apiClearChat: return "apiClearChat" + case .apiListContacts: return "apiListContacts" + case .apiUpdateProfile: return "apiUpdateProfile" + case .apiSetContactPrefs: return "apiSetContactPrefs" + case .apiSetContactAlias: return "apiSetContactAlias" + case .apiSetGroupAlias: return "apiSetGroupAlias" + case .apiSetConnectionAlias: return "apiSetConnectionAlias" + case .apiSetUserUIThemes: return "apiSetUserUIThemes" + case .apiSetChatUIThemes: return "apiSetChatUIThemes" + case .apiCreateMyAddress: return "apiCreateMyAddress" + case .apiDeleteMyAddress: return "apiDeleteMyAddress" + case .apiShowMyAddress: return "apiShowMyAddress" + case .apiSetProfileAddress: return "apiSetProfileAddress" + case .apiAddressAutoAccept: return "apiAddressAutoAccept" + case .apiAcceptContact: return "apiAcceptContact" + case .apiRejectContact: return "apiRejectContact" + case .apiSendCallInvitation: return "apiSendCallInvitation" + case .apiRejectCall: return "apiRejectCall" + case .apiSendCallOffer: return "apiSendCallOffer" + case .apiSendCallAnswer: return "apiSendCallAnswer" + case .apiSendCallExtraInfo: return "apiSendCallExtraInfo" + case .apiEndCall: return "apiEndCall" + case .apiGetCallInvitations: return "apiGetCallInvitations" + case .apiCallStatus: return "apiCallStatus" + case .apiGetNetworkStatuses: return "apiGetNetworkStatuses" + case .apiChatRead: return "apiChatRead" + case .apiChatItemsRead: return "apiChatItemsRead" + case .apiChatUnread: return "apiChatUnread" + case .receiveFile: return "receiveFile" + case .setFileToReceive: return "setFileToReceive" + case .cancelFile: return "cancelFile" + case .setLocalDeviceName: return "setLocalDeviceName" + case .connectRemoteCtrl: return "connectRemoteCtrl" + case .findKnownRemoteCtrl: return "findKnownRemoteCtrl" + case .confirmRemoteCtrl: return "confirmRemoteCtrl" + case .verifyRemoteCtrlSession: return "verifyRemoteCtrlSession" + case .listRemoteCtrls: return "listRemoteCtrls" + case .stopRemoteCtrl: return "stopRemoteCtrl" + case .deleteRemoteCtrl: return "deleteRemoteCtrl" + case .apiUploadStandaloneFile: return "apiUploadStandaloneFile" + case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" + case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" + case .showVersion: return "showVersion" + case .getAgentSubsTotal: return "getAgentSubsTotal" + case .getAgentServersSummary: return "getAgentServersSummary" + case .resetAgentServersStats: return "resetAgentServersStats" + case .string: return "console command" + } + } + } + + func ref(_ type: ChatType, _ id: Int64) -> String { + "\(type.rawValue)\(id)" + } + + func joinedIds(_ ids: [Int64]) -> String { + ids.map { "\($0)" }.joined(separator: ",") + } + + func chatItemTTLStr(seconds: Int64?) -> String { + if let seconds = seconds { + return String(seconds) + } else { + return "default" + } + } + + var obfuscated: ChatCommand { + switch self { + case let .apiStorageEncryption(cfg): + return .apiStorageEncryption(config: DBEncryptionConfig(currentKey: obfuscate(cfg.currentKey), newKey: obfuscate(cfg.newKey))) + case let .apiSetActiveUser(userId, viewPwd): + return .apiSetActiveUser(userId: userId, viewPwd: obfuscate(viewPwd)) + case let .apiHideUser(userId, viewPwd): + return .apiHideUser(userId: userId, viewPwd: obfuscate(viewPwd)) + case let .apiUnhideUser(userId, viewPwd): + return .apiUnhideUser(userId: userId, viewPwd: obfuscate(viewPwd)) + case let .apiDeleteUser(userId, delSMPQueues, viewPwd): + return .apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: obfuscate(viewPwd)) + case let .testStorageEncryption(key): + return .testStorageEncryption(key: obfuscate(key)) + default: return self + } + } + + private func obfuscate(_ s: String) -> String { + s == "" ? "" : "***" + } + + private func obfuscate(_ s: String?) -> String? { + if let s = s { + return obfuscate(s) + } + return nil + } + + private func onOffParam(_ param: String, _ b: Bool?) -> String { + if let b = b { + return " \(param)=\(onOff(b))" + } + return "" + } + + private func maybePwd(_ pwd: String?) -> String { + pwd == "" || pwd == nil ? "" : " " + encodeJSON(pwd) + } +} + +// ChatResponse is split to three enums to reduce stack size used when parsing it, parsing large enums is very inefficient. +enum ChatResponse0: Decodable, ChatAPIResult { + case activeUser(user: User) + case usersList(users: [UserInfo]) + case chatStarted + case chatRunning + case chatStopped + case apiChats(user: UserRef, chats: [ChatData]) + case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo?) + case chatTags(user: UserRef, userTags: [ChatTag]) + case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) + case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) + case serverOperatorConditions(conditions: ServerOperatorConditions) + case userServers(user: UserRef, userServers: [UserOperatorServers]) + case userServersValidation(user: UserRef, serverErrors: [UserServersError]) + case usageConditions(usageConditions: UsageConditions, conditionsText: String, acceptedConditions: UsageConditions?) + case chatItemTTL(user: UserRef, chatItemTTL: Int64?) + case networkConfig(networkConfig: NetCfg) + case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) + case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) + case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: ServerQueueInfo) + case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) + case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) + case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) + case groupMemberSwitchAborted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) + case contactRatchetSyncStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) + case groupMemberRatchetSyncStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) + case contactCode(user: UserRef, contact: Contact, connectionCode: String) + case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) + case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) + case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64]) + + var responseType: String { + switch self { + case .activeUser: "activeUser" + case .usersList: "usersList" + case .chatStarted: "chatStarted" + case .chatRunning: "chatRunning" + case .chatStopped: "chatStopped" + case .apiChats: "apiChats" + case .apiChat: "apiChat" + case .chatTags: "chatTags" + case .chatItemInfo: "chatItemInfo" + case .serverTestResult: "serverTestResult" + case .serverOperatorConditions: "serverOperators" + case .userServers: "userServers" + case .userServersValidation: "userServersValidation" + case .usageConditions: "usageConditions" + case .chatItemTTL: "chatItemTTL" + case .networkConfig: "networkConfig" + case .contactInfo: "contactInfo" + case .groupMemberInfo: "groupMemberInfo" + case .queueInfo: "queueInfo" + case .contactSwitchStarted: "contactSwitchStarted" + case .groupMemberSwitchStarted: "groupMemberSwitchStarted" + case .contactSwitchAborted: "contactSwitchAborted" + case .groupMemberSwitchAborted: "groupMemberSwitchAborted" + case .contactRatchetSyncStarted: "contactRatchetSyncStarted" + case .groupMemberRatchetSyncStarted: "groupMemberRatchetSyncStarted" + case .contactCode: "contactCode" + case .groupMemberCode: "groupMemberCode" + case .connectionVerified: "connectionVerified" + case .tagsUpdated: "tagsUpdated" + } + } + + var details: String { + switch self { + case let .activeUser(user): return String(describing: user) + case let .usersList(users): return String(describing: users) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case .chatStopped: return noDetails + case let .apiChats(u, chats): return withUser(u, String(describing: chats)) + case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))") + case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))") + case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") + case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") + case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))" + case let .userServers(u, userServers): return withUser(u, "userServers: \(String(describing: userServers))") + case let .userServersValidation(u, serverErrors): return withUser(u, "serverErrors: \(String(describing: serverErrors))") + case let .usageConditions(usageConditions, _, acceptedConditions): return "usageConditions: \(String(describing: usageConditions))\nacceptedConditions: \(String(describing: acceptedConditions))" + case let .chatItemTTL(u, chatItemTTL): return withUser(u, String(describing: chatItemTTL)) + case let .networkConfig(networkConfig): return String(describing: networkConfig) + case let .contactInfo(u, contact, connectionStats_, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats_: \(String(describing: connectionStats_))\ncustomUserProfile: \(String(describing: customUserProfile))") + case let .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") + case let .queueInfo(u, rcvMsgInfo, queueInfo): + let msgInfo = if let info = rcvMsgInfo { encodeJSON(info) } else { "none" } + return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") + case let .contactSwitchStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") + case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") + case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") + case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))") + } + } + + static func fallbackResult(_ type: String, _ json: NSDictionary) -> ChatResponse0? { + if type == "apiChats" { + if let r = parseApiChats(json) { + return .apiChats(user: r.user, chats: r.chats) + } + } else if type == "apiChat" { + if let jApiChat = json["apiChat"] as? NSDictionary, + let user: UserRef = try? decodeObject(jApiChat["user"] as Any), + let jChat = jApiChat["chat"] as? NSDictionary, + let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) { + return .apiChat(user: user, chat: chat, navInfo: navInfo) + } + } + return nil + } +} + +enum ChatResponse1: Decodable, ChatAPIResult { + case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection) + case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) + case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) + case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan) + case sentConfirmation(user: UserRef, connection: PendingContactConnection) + case sentInvitation(user: UserRef, connection: PendingContactConnection) + case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) + case contactAlreadyExists(user: UserRef, contact: Contact) + case contactDeleted(user: UserRef, contact: Contact) + case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) + case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) + case chatCleared(user: UserRef, chatInfo: ChatInfo) + case userProfileNoChange(user: User) + case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) + case userPrivacy(user: User, updatedUser: User) + case contactAliasUpdated(user: UserRef, toContact: Contact) + case groupAliasUpdated(user: UserRef, toGroup: GroupInfo) + case connectionAliasUpdated(user: UserRef, toConnection: PendingContactConnection) + case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact) + case userContactLink(user: User, contactLink: UserContactLink) + case userContactLinkUpdated(user: User, contactLink: UserContactLink) + case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink) + case userContactLinkDeleted(user: User) + case acceptingContactRequest(user: UserRef, contact: Contact) + case contactRequestRejected(user: UserRef) + case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) + case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?) + case chatItemUpdated(user: UserRef, chatItem: AChatItem) + case chatItemNotChanged(user: UserRef, chatItem: AChatItem) + case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) + case reactionMembers(user: UserRef, memberReactions: [MemberReaction]) + case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) + case contactsList(user: UserRef, contacts: [Contact]) + + var responseType: String { + switch self { + case .invitation: "invitation" + case .connectionIncognitoUpdated: "connectionIncognitoUpdated" + case .connectionUserChanged: "connectionUserChanged" + case .connectionPlan: "connectionPlan" + case .sentConfirmation: "sentConfirmation" + case .sentInvitation: "sentInvitation" + case .sentInvitationToContact: "sentInvitationToContact" + case .contactAlreadyExists: "contactAlreadyExists" + case .contactDeleted: "contactDeleted" + case .contactConnectionDeleted: "contactConnectionDeleted" + case .groupDeletedUser: "groupDeletedUser" + case .chatCleared: "chatCleared" + case .userProfileNoChange: "userProfileNoChange" + case .userProfileUpdated: "userProfileUpdated" + case .userPrivacy: "userPrivacy" + case .contactAliasUpdated: "contactAliasUpdated" + case .groupAliasUpdated: "groupAliasUpdated" + case .connectionAliasUpdated: "connectionAliasUpdated" + case .contactPrefsUpdated: "contactPrefsUpdated" + case .userContactLink: "userContactLink" + case .userContactLinkUpdated: "userContactLinkUpdated" + case .userContactLinkCreated: "userContactLinkCreated" + case .userContactLinkDeleted: "userContactLinkDeleted" + case .acceptingContactRequest: "acceptingContactRequest" + case .contactRequestRejected: "contactRequestRejected" + case .networkStatuses: "networkStatuses" + case .newChatItems: "newChatItems" + case .groupChatItemsDeleted: "groupChatItemsDeleted" + case .forwardPlan: "forwardPlan" + case .chatItemUpdated: "chatItemUpdated" + case .chatItemNotChanged: "chatItemNotChanged" + case .chatItemReaction: "chatItemReaction" + case .reactionMembers: "reactionMembers" + case .chatItemsDeleted: "chatItemsDeleted" + case .contactsList: "contactsList" + } + } + + var details: String { + switch self { + case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) + case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) + case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) + case .userProfileNoChange: return noDetails + case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) + case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) + case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) + case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") + case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) + case .userContactLinkDeleted: return noDetails + case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact)) + case .contactRequestRejected: return noDetails + case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): + return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") + case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") + case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))") + case let .chatItemsDeleted(u, items, byUser): + let itemsString = items.map { item in + "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") + return withUser(u, itemsString + "\nbyUser: \(byUser)") + case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) + case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)") + case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") + case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") + case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) + case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) + case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) + case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) + } + } +} + +enum ChatResponse2: Decodable, ChatAPIResult { + // group responses + case groupCreated(user: UserRef, groupInfo: GroupInfo) + case sentGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, member: GroupMember) + case userAcceptedGroupSent(user: UserRef, groupInfo: GroupInfo, hostContact: Contact?) + case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) + case leftMemberUser(user: UserRef, groupInfo: GroupInfo) + case groupMembers(user: UserRef, group: SimpleXChat.Group) + case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) + case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) + case groupUpdated(user: UserRef, toGroup: GroupInfo) + case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) + case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) + case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) + case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + // receiving file responses + case rcvFileAccepted(user: UserRef, chatItem: AChatItem) + case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case standaloneFileInfo(fileMeta: MigrationFileLinkData?) + case rcvStandaloneFileCreated(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case rcvFileCancelled(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) + // sending file responses + case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) + case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload + case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used + case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) + // call invitations + case callInvitations(callInvitations: [RcvCallInvitation]) + // notifications + case ntfTokenStatus(status: NtfTknStatus) + case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) + case ntfConns(ntfConns: [NtfConn]) + case connNtfMessages(receivedMsgs: [RcvNtfMsgInfo]) + // remote desktop responses + case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo]) + case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String) + case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo) + // misc + case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) + case cmdOk(user_: UserRef?) + case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) + case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) + case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) + case archiveExported(archiveErrors: [ArchiveError]) + case archiveImported(archiveErrors: [ArchiveError]) + case appSettings(appSettings: AppSettings) + + var responseType: String { + switch self { + case .groupCreated: "groupCreated" + case .sentGroupInvitation: "sentGroupInvitation" + case .userAcceptedGroupSent: "userAcceptedGroupSent" + case .userDeletedMembers: "userDeletedMembers" + case .leftMemberUser: "leftMemberUser" + case .groupMembers: "groupMembers" + case .membersRoleUser: "membersRoleUser" + case .membersBlockedForAllUser: "membersBlockedForAllUser" + case .groupUpdated: "groupUpdated" + case .groupLinkCreated: "groupLinkCreated" + case .groupLink: "groupLink" + case .groupLinkDeleted: "groupLinkDeleted" + case .newMemberContact: "newMemberContact" + case .newMemberContactSentInv: "newMemberContactSentInv" + case .rcvFileAccepted: "rcvFileAccepted" + case .rcvFileAcceptedSndCancelled: "rcvFileAcceptedSndCancelled" + case .standaloneFileInfo: "standaloneFileInfo" + case .rcvStandaloneFileCreated: "rcvStandaloneFileCreated" + case .rcvFileCancelled: "rcvFileCancelled" + case .sndFileCancelled: "sndFileCancelled" + case .sndStandaloneFileCreated: "sndStandaloneFileCreated" + case .sndFileStartXFTP: "sndFileStartXFTP" + case .sndFileCancelledXFTP: "sndFileCancelledXFTP" + case .callInvitations: "callInvitations" + case .ntfTokenStatus: "ntfTokenStatus" + case .ntfToken: "ntfToken" + case .ntfConns: "ntfConns" + case .connNtfMessages: "connNtfMessages" + case .remoteCtrlList: "remoteCtrlList" + case .remoteCtrlConnecting: "remoteCtrlConnecting" + case .remoteCtrlConnected: "remoteCtrlConnected" + case .versionInfo: "versionInfo" + case .cmdOk: "cmdOk" + case .agentSubsTotal: "agentSubsTotal" + case .agentServersSummary: "agentServersSummary" + case .agentSubsSummary: "agentSubsSummary" + case .archiveExported: "archiveExported" + case .archiveImported: "archiveImported" + case .appSettings: "appSettings" + } + } + + var details: String { + switch self { + case let .groupCreated(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .sentGroupInvitation(u, groupInfo, contact, member): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmember: \(member)") + case let .userAcceptedGroupSent(u, groupInfo, hostContact): return withUser(u, "groupInfo: \(groupInfo)\nhostContact: \(String(describing: hostContact))") + case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") + case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .groupMembers(u, group): return withUser(u, String(describing: group)) + case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") + case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") + case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case .rcvFileAcceptedSndCancelled: return noDetails + case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) + case .rcvStandaloneFileCreated: return noDetails + case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) + case .sndStandaloneFileCreated: return noDetails + case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .callInvitations(invs): return String(describing: invs) + case let .ntfTokenStatus(status): return String(describing: status) + case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" + case let .ntfConns(ntfConns): return String(describing: ntfConns) + case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" + case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls) + case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)" + case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) + case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))" + case .cmdOk: return noDetails + case let .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") + case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) + case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) + case let .archiveExported(archiveErrors): return String(describing: archiveErrors) + case let .archiveImported(archiveErrors): return String(describing: archiveErrors) + case let .appSettings(appSettings): return String(describing: appSettings) + } + } +} + +enum ChatEvent: Decodable, ChatAPIResult { + case chatSuspended + case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) + case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) + case contactRatchetSync(user: UserRef, contact: Contact, ratchetSyncProgress: RatchetSyncProgress) + case groupMemberRatchetSync(user: UserRef, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress) + case contactDeletedByContact(user: UserRef, contact: Contact) + case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) + case contactConnecting(user: UserRef, contact: Contact) + case contactSndReady(user: UserRef, contact: Contact) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + case contactUpdated(user: UserRef, toContact: Contact) + case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember) + case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) + case networkStatus(networkStatus: NetworkStatus, connections: [String]) + case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) + case chatItemUpdated(user: UserRef, chatItem: AChatItem) + case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) + case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) + // group events + case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) + case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) + case userAcceptedGroupSent(user: UserRef, groupInfo: GroupInfo, hostContact: Contact?) + case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember) + case businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) + case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) + case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) + case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) + case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) + case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember, withMessages: Bool) + case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case userJoinedGroup(user: UserRef, groupInfo: GroupInfo) + case joinedGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) + case groupUpdated(user: UserRef, toGroup: GroupInfo) + case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + // receiving file events + case rcvFileAccepted(user: UserRef, chatItem: AChatItem) + case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats + case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) + case rcvFileComplete(user: UserRef, chatItem: AChatItem) + case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) + case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case rcvFileError(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) + case rcvFileWarning(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) + // sending file events + case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) + case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) + case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + // call events + case callInvitation(callInvitation: RcvCallInvitation) + case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) + case callAnswer(user: UserRef, contact: Contact, answer: WebRTCSession) + case callExtraInfo(user: UserRef, contact: Contact, extraInfo: WebRTCExtraInfo) + case callEnded(user: UserRef, contact: Contact) + case contactDisabled(user: UserRef, contact: Contact) + // notification marker + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + // remote desktop responses + case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool) + case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String) + case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo) + case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) + // pq + case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) + + var responseType: String { + switch self { + case .chatSuspended: "chatSuspended" + case .contactSwitch: "contactSwitch" + case .groupMemberSwitch: "groupMemberSwitch" + case .contactRatchetSync: "contactRatchetSync" + case .groupMemberRatchetSync: "groupMemberRatchetSync" + case .contactDeletedByContact: "contactDeletedByContact" + case .contactConnected: "contactConnected" + case .contactConnecting: "contactConnecting" + case .contactSndReady: "contactSndReady" + case .receivedContactRequest: "receivedContactRequest" + case .contactUpdated: "contactUpdated" + case .groupMemberUpdated: "groupMemberUpdated" + case .contactsMerged: "contactsMerged" + case .networkStatus: "networkStatus" + case .networkStatuses: "networkStatuses" + case .newChatItems: "newChatItems" + case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" + case .chatItemUpdated: "chatItemUpdated" + case .chatItemReaction: "chatItemReaction" + case .chatItemsDeleted: "chatItemsDeleted" + case .groupChatItemsDeleted: "groupChatItemsDeleted" + case .receivedGroupInvitation: "receivedGroupInvitation" + case .userAcceptedGroupSent: "userAcceptedGroupSent" + case .groupLinkConnecting: "groupLinkConnecting" + case .businessLinkConnecting: "businessLinkConnecting" + case .joinedGroupMemberConnecting: "joinedGroupMemberConnecting" + case .memberRole: "memberRole" + case .memberBlockedForAll: "memberBlockedForAll" + case .deletedMemberUser: "deletedMemberUser" + case .deletedMember: "deletedMember" + case .leftMember: "leftMember" + case .groupDeleted: "groupDeleted" + case .userJoinedGroup: "userJoinedGroup" + case .joinedGroupMember: "joinedGroupMember" + case .connectedToGroupMember: "connectedToGroupMember" + case .groupUpdated: "groupUpdated" + case .newMemberContactReceivedInv: "newMemberContactReceivedInv" + case .rcvFileAccepted: "rcvFileAccepted" + case .rcvFileAcceptedSndCancelled: "rcvFileAcceptedSndCancelled" + case .rcvFileStart: "rcvFileStart" + case .rcvFileProgressXFTP: "rcvFileProgressXFTP" + case .rcvFileComplete: "rcvFileComplete" + case .rcvStandaloneFileComplete: "rcvStandaloneFileComplete" + case .rcvFileSndCancelled: "rcvFileSndCancelled" + case .rcvFileError: "rcvFileError" + case .rcvFileWarning: "rcvFileWarning" + case .sndFileStart: "sndFileStart" + case .sndFileComplete: "sndFileComplete" + case .sndFileRcvCancelled: "sndFileRcvCancelled" + case .sndFileProgressXFTP: "sndFileProgressXFTP" + case .sndFileRedirectStartXFTP: "sndFileRedirectStartXFTP" + case .sndFileCompleteXFTP: "sndFileCompleteXFTP" + case .sndStandaloneFileComplete: "sndStandaloneFileComplete" + case .sndFileError: "sndFileError" + case .sndFileWarning: "sndFileWarning" + case .callInvitation: "callInvitation" + case .callOffer: "callOffer" + case .callAnswer: "callAnswer" + case .callExtraInfo: "callExtraInfo" + case .callEnded: "callEnded" + case .contactDisabled: "contactDisabled" + case .ntfMessage: "ntfMessage" + case .remoteCtrlFound: "remoteCtrlFound" + case .remoteCtrlSessionCode: "remoteCtrlSessionCode" + case .remoteCtrlConnected: "remoteCtrlConnected" + case .remoteCtrlStopped: "remoteCtrlStopped" + case .contactPQEnabled: "contactPQEnabled" + } + } + + var details: String { + switch self { + case .chatSuspended: return noDetails + case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") + case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") + case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") + case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") + case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) + case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) + case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) + case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) + case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact)) + case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)") + case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") + case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" + case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .chatItemsDeleted(u, items, byUser): + let itemsString = items.map { item in + "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") + return withUser(u, itemsString + "\nbyUser: \(byUser)") + case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): + return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") + case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") + case let .userAcceptedGroupSent(u, groupInfo, hostContact): return withUser(u, "groupInfo: \(groupInfo)\nhostContact: \(String(describing: hostContact))") + case let .groupLinkConnecting(u, groupInfo, hostMember): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))") + case let .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") + case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") + case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") + case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") + case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") + case let .deletedMember(u, groupInfo, byMember, deletedMember, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)\nwithMessages: \(withMessages)") + case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") + case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case .rcvFileAcceptedSndCancelled: return noDetails + case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") + case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) + case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .rcvFileError(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") + case let .rcvFileWarning(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") + case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .callInvitation(inv): return String(describing: inv) + case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") + case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") + case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))") + case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") + case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)" + case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)" + case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) + case let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" + case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") + } + } +} + +struct NewUser: Encodable { + var profile: Profile? + var pastTimestamp: Bool +} + +enum ChatPagination { + static let INITIAL_COUNT = 75 + static let PRELOAD_COUNT = 100 + static let UNTIL_PRELOAD_COUNT = 50 + + case last(count: Int) + case after(chatItemId: Int64, count: Int) + case before(chatItemId: Int64, count: Int) + case around(chatItemId: Int64, count: Int) + case initial(count: Int) + + var cmdString: String { + switch self { + case let .last(count): return "count=\(count)" + case let .after(chatItemId, count): return "after=\(chatItemId) count=\(count)" + case let .before(chatItemId, count): return "before=\(chatItemId) count=\(count)" + case let .around(chatItemId, count): return "around=\(chatItemId) count=\(count)" + case let .initial(count): return "initial=\(count)" + } + } +} + +enum ConnectionPlan: Decodable, Hashable { + case invitationLink(invitationLinkPlan: InvitationLinkPlan) + case contactAddress(contactAddressPlan: ContactAddressPlan) + case groupLink(groupLinkPlan: GroupLinkPlan) + case error(chatError: ChatError) +} + +enum InvitationLinkPlan: Decodable, Hashable { + case ok + case ownLink + case connecting(contact_: Contact?) + case known(contact: Contact) +} + +enum ContactAddressPlan: Decodable, Hashable { + case ok + case ownLink + case connectingConfirmReconnect + case connectingProhibit(contact: Contact) + case known(contact: Contact) + case contactViaAddress(contact: Contact) +} + +enum GroupLinkPlan: Decodable, Hashable { + case ok + case ownLink(groupInfo: GroupInfo) + case connectingConfirmReconnect + case connectingProhibit(groupInfo_: GroupInfo?) + case known(groupInfo: GroupInfo) +} + +struct ChatTagData: Encodable { + var emoji: String? + var text: String +} + +struct UpdatedMessage: Encodable { + var msgContent: MsgContent + var mentions: [String: Int64] + + var cmdString: String { + "json \(encodeJSON(self))" + } +} + +enum ChatDeleteMode: Codable { + case full(notify: Bool) + case entity(notify: Bool) + case messages + + var cmdString: String { + switch self { + case let .full(notify): "full notify=\(onOff(notify))" + case let .entity(notify): "entity notify=\(onOff(notify))" + case .messages: "messages" + } + } + + var isEntity: Bool { + switch self { + case .entity: return true + default: return false + } + } +} + +enum NetworkStatus: Decodable, Equatable { + case unknown + case connected + case disconnected + case error(connectionError: String) + + var statusString: LocalizedStringKey { + switch self { + case .connected: "connected" + case .error: "error" + default: "connecting" + } + } + + var statusExplanation: LocalizedStringKey { + switch self { + case .connected: "You are connected to the server used to receive messages from this contact." + case let .error(err): "Trying to connect to the server used to receive messages from this contact (error: \(err))." + default: "Trying to connect to the server used to receive messages from this contact." + } + } + + var imageName: String { + switch self { + case .unknown: "circle.dotted" + case .connected: "circle.fill" + case .disconnected: "ellipsis.circle.fill" + case .error: "exclamationmark.circle.fill" + } + } +} + +enum ForwardConfirmation: Decodable, Hashable { + case filesNotAccepted(fileIds: [Int64]) + case filesInProgress(filesCount: Int) + case filesMissing(filesCount: Int) + case filesFailed(filesCount: Int) +} + +struct ConnNetworkStatus: Decodable { + var agentConnId: String + var networkStatus: NetworkStatus +} + +struct UserMsgReceiptSettings: Codable { + var enable: Bool + var clearOverrides: Bool +} + + +struct UserContactLink: Decodable, Hashable { + var connLinkContact: CreatedConnLink + var autoAccept: AutoAccept? + + var responseDetails: String { + "connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" + } +} + +struct AutoAccept: Codable, Hashable { + var businessAddress: Bool + var acceptIncognito: Bool + var autoReply: MsgContent? + + static func cmdString(_ autoAccept: AutoAccept?) -> String { + guard let autoAccept = autoAccept else { return "off" } + var s = "on" + if autoAccept.acceptIncognito { + s += " incognito=on" + } else if autoAccept.businessAddress { + s += " business" + } + guard let msg = autoAccept.autoReply else { return s } + return s + " " + msg.cmdString + } +} + +struct DeviceToken: Decodable { + var pushProvider: PushProvider + var token: String + + var cmdString: String { + "\(pushProvider) \(token)" + } +} + +enum PushEnvironment: String { + case development + case production +} + +enum PushProvider: String, Decodable { + case apns_dev + case apns_prod + + init(env: PushEnvironment) { + switch env { + case .development: self = .apns_dev + case .production: self = .apns_prod + } + } +} + +// This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, +// and .local for periodic background checks +enum NotificationsMode: String, Decodable, SelectableItem { + case off = "OFF" + case periodic = "PERIODIC" + case instant = "INSTANT" + + var label: LocalizedStringKey { + switch self { + case .off: "No push server" + case .periodic: "Periodic" + case .instant: "Instant" + } + } + + var icon: String { + switch self { + case .off: return "arrow.clockwise" + case .periodic: return "timer" + case .instant: return "bolt" + } + } + + var id: String { self.rawValue } + + static var values: [NotificationsMode] = [.instant, .periodic, .off] +} + +struct RemoteCtrlInfo: Decodable { + var remoteCtrlId: Int64 + var ctrlDeviceName: String + var sessionState: RemoteCtrlSessionState? + + var deviceViewName: String { + ctrlDeviceName == "" ? "\(remoteCtrlId)" : ctrlDeviceName + } +} + +enum RemoteCtrlSessionState: Decodable { + case starting + case searching + case connecting + case pendingConfirmation(sessionCode: String) + case connected(sessionCode: String) +} + +enum RemoteCtrlStopReason: Decodable { + case discoveryFailed(chatError: ChatError) + case connectionFailed(chatError: ChatError) + case setupFailed(chatError: ChatError) + case disconnected +} + +struct CtrlAppInfo: Decodable { + var appVersionRange: AppVersionRange + var deviceName: String +} + +struct AppVersionRange: Decodable { + var minVersion: String + var maxVersion: String +} + +struct CoreVersionInfo: Decodable { + var version: String + var simplexmqVersion: String + var simplexmqCommit: String +} + +struct ArchiveConfig: Encodable { + var archivePath: String + var disableCompression: Bool? +} + +struct DBEncryptionConfig: Codable { + var currentKey: String + var newKey: String +} + +enum OperatorTag: String, Codable { + case simplex = "simplex" + case flux = "flux" +} + +struct ServerOperatorInfo { + var description: [String] + var website: URL + var selfhost: (text: String, link: URL)? = nil + var logo: String + var largeLogo: String + var logoDarkMode: String + var largeLogoDarkMode: String +} + +let operatorsInfo: Dictionary = [ + .simplex: ServerOperatorInfo( + description: [ + "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or identity keys.", + "SimpleX Chat Ltd develops the communication software for SimpleX network." + ], + website: URL(string: "https://simplex.chat")!, + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ), + .flux: ServerOperatorInfo( + description: [ + "Flux is the largest decentralized cloud, based on a global network of user-operated nodes.", + "Flux offers a powerful, scalable, and affordable cutting edge technology platform for all.", + "Flux operates servers in SimpleX network to improve its privacy and decentralization." + ], + website: URL(string: "https://runonflux.com")!, + selfhost: (text: "Self-host SimpleX servers on Flux", link: URL(string: "https://home.runonflux.io/apps/marketplace?q=simplex")!), + logo: "flux_logo_symbol", + largeLogo: "flux_logo", + logoDarkMode: "flux_logo_symbol", + largeLogoDarkMode: "flux_logo-light" + ), +] + +struct UsageConditions: Decodable { + var conditionsId: Int64 + var conditionsCommit: String + var notifiedAt: Date? + var createdAt: Date + + static var sampleData = UsageConditions( + conditionsId: 1, + conditionsCommit: "11a44dc1fd461a93079f897048b46998db55da5c", + notifiedAt: nil, + createdAt: Date.now + ) +} + +enum UsageConditionsAction: Decodable { + case review(operators: [ServerOperator], deadline: Date?, showNotice: Bool) + case accepted(operators: [ServerOperator]) + + var showNotice: Bool { + switch self { + case let .review(_, _, showNotice): showNotice + case .accepted: false + } + } +} + +struct ServerOperatorConditions: Decodable { + var serverOperators: [ServerOperator] + var currentConditions: UsageConditions + var conditionsAction: UsageConditionsAction? + + static var empty = ServerOperatorConditions( + serverOperators: [], + currentConditions: UsageConditions(conditionsId: 0, conditionsCommit: "empty", notifiedAt: nil, createdAt: .now), + conditionsAction: nil + ) +} + +enum ConditionsAcceptance: Equatable, Codable, Hashable { + case accepted(acceptedAt: Date?, autoAccepted: Bool) + // If deadline is present, it means there's a grace period to review and accept conditions during which user can continue to use the operator. + // No deadline indicates it's required to accept conditions for the operator to start using it. + case required(deadline: Date?) + + var conditionsAccepted: Bool { + switch self { + case .accepted: true + case .required: false + } + } + + var usageAllowed: Bool { + switch self { + case .accepted: true + case let .required(deadline): deadline != nil + } + } +} + +struct ServerOperator: Identifiable, Equatable, Codable { + var operatorId: Int64 + var operatorTag: OperatorTag? + var tradeName: String + var legalName: String? + var serverDomains: [String] + var conditionsAcceptance: ConditionsAcceptance + var enabled: Bool + var smpRoles: ServerRoles + var xftpRoles: ServerRoles + + var id: Int64 { operatorId } + + static func == (l: ServerOperator, r: ServerOperator) -> Bool { + l.operatorId == r.operatorId && l.operatorTag == r.operatorTag && l.tradeName == r.tradeName && l.legalName == r.legalName && + l.serverDomains == r.serverDomains && l.conditionsAcceptance == r.conditionsAcceptance && l.enabled == r.enabled && + l.smpRoles == r.smpRoles && l.xftpRoles == r.xftpRoles + } + + var legalName_: String { + legalName ?? tradeName + } + + var info: ServerOperatorInfo { + return if let operatorTag = operatorTag { + operatorsInfo[operatorTag] ?? ServerOperator.dummyOperatorInfo + } else { + ServerOperator.dummyOperatorInfo + } + } + + static let dummyOperatorInfo = ServerOperatorInfo( + description: ["Default"], + website: URL(string: "https://simplex.chat")!, + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ) + + func logo(_ colorScheme: ColorScheme) -> String { + colorScheme == .light ? info.logo : info.logoDarkMode + } + + func largeLogo(_ colorScheme: ColorScheme) -> String { + colorScheme == .light ? info.largeLogo : info.largeLogoDarkMode + } + + static var sampleData1 = ServerOperator( + operatorId: 1, + operatorTag: .simplex, + tradeName: "SimpleX Chat", + legalName: "SimpleX Chat Ltd", + serverDomains: ["simplex.im"], + conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false), + enabled: true, + smpRoles: ServerRoles(storage: true, proxy: true), + xftpRoles: ServerRoles(storage: true, proxy: true) + ) +} + +struct ServerRoles: Equatable, Codable { + var storage: Bool + var proxy: Bool +} + +struct UserOperatorServers: Identifiable, Equatable, Codable { + var `operator`: ServerOperator? + var smpServers: [UserServer] + var xftpServers: [UserServer] + + var id: String { + if let op = self.operator { + "\(op.operatorId)" + } else { + "nil operator" + } + } + + var operator_: ServerOperator { + get { + self.operator ?? ServerOperator( + operatorId: 0, + operatorTag: nil, + tradeName: "", + legalName: "", + serverDomains: [], + conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false), + enabled: false, + smpRoles: ServerRoles(storage: true, proxy: true), + xftpRoles: ServerRoles(storage: true, proxy: true) + ) + } + set { `operator` = newValue } + } + + static var sampleData1 = UserOperatorServers( + operator: ServerOperator.sampleData1, + smpServers: [UserServer.sampleData.preset], + xftpServers: [UserServer.sampleData.xftpPreset] + ) + + static var sampleDataNilOperator = UserOperatorServers( + operator: nil, + smpServers: [UserServer.sampleData.preset], + xftpServers: [UserServer.sampleData.xftpPreset] + ) +} + +enum UserServersError: Decodable { + case noServers(protocol: ServerProtocol, user: UserRef?) + case storageMissing(protocol: ServerProtocol, user: UserRef?) + case proxyMissing(protocol: ServerProtocol, user: UserRef?) + case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String) + + var globalError: String? { + switch self { + case let .noServers(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + case let .storageMissing(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + case let .proxyMissing(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + default: return nil + } + } + + var globalSMPError: String? { + switch self { + case let .noServers(.smp, user): + let text = NSLocalizedString("No message servers.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .storageMissing(.smp, user): + let text = NSLocalizedString("No servers to receive messages.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .proxyMissing(.smp, user): + let text = NSLocalizedString("No servers for private message routing.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + default: + return nil + } + } + + var globalXFTPError: String? { + switch self { + case let .noServers(.xftp, user): + let text = NSLocalizedString("No media & file servers.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .storageMissing(.xftp, user): + let text = NSLocalizedString("No servers to send files.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .proxyMissing(.xftp, user): + let text = NSLocalizedString("No servers to receive files.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + default: + return nil + } + } + + private func userStr(_ user: UserRef) -> String { + String.localizedStringWithFormat(NSLocalizedString("For chat profile %@:", comment: "servers error"), user.localDisplayName) + } +} + +struct UserServer: Identifiable, Equatable, Codable, Hashable { + var serverId: Int64? + var server: String + var preset: Bool + var tested: Bool? + var enabled: Bool + var deleted: Bool + var createdAt = Date() + + static func == (l: UserServer, r: UserServer) -> Bool { + l.serverId == r.serverId && l.server == r.server && l.preset == r.preset && l.tested == r.tested && + l.enabled == r.enabled && l.deleted == r.deleted + } + + var id: String { "\(server) \(createdAt)" } + + static var empty = UserServer(serverId: nil, server: "", preset: false, tested: nil, enabled: false, deleted: false) + + var isEmpty: Bool { + server.trimmingCharacters(in: .whitespaces) == "" + } + + struct SampleData { + var preset: UserServer + var custom: UserServer + var untested: UserServer + var xftpPreset: UserServer + } + + static var sampleData = SampleData( + preset: UserServer( + serverId: 1, + server: "smp://abcd@smp8.simplex.im", + preset: true, + tested: true, + enabled: true, + deleted: false + ), + custom: UserServer( + serverId: 2, + server: "smp://abcd@smp9.simplex.im", + preset: false, + tested: false, + enabled: false, + deleted: false + ), + untested: UserServer( + serverId: 3, + server: "smp://abcd@smp10.simplex.im", + preset: false, + tested: nil, + enabled: true, + deleted: false + ), + xftpPreset: UserServer( + serverId: 4, + server: "xftp://abcd@xftp8.simplex.im", + preset: true, + tested: true, + enabled: true, + deleted: false + ) + ) + + enum CodingKeys: CodingKey { + case serverId + case server + case preset + case tested + case enabled + case deleted + } +} + +enum ProtocolTestStep: String, Decodable, Equatable { + case connect + case disconnect + case createQueue + case secureQueue + case deleteQueue + case createFile + case uploadFile + case downloadFile + case compareFile + case deleteFile + + var text: String { + switch self { + case .connect: return NSLocalizedString("Connect", comment: "server test step") + case .disconnect: return NSLocalizedString("Disconnect", comment: "server test step") + case .createQueue: return NSLocalizedString("Create queue", comment: "server test step") + case .secureQueue: return NSLocalizedString("Secure queue", comment: "server test step") + case .deleteQueue: return NSLocalizedString("Delete queue", comment: "server test step") + case .createFile: return NSLocalizedString("Create file", comment: "server test step") + case .uploadFile: return NSLocalizedString("Upload file", comment: "server test step") + case .downloadFile: return NSLocalizedString("Download file", comment: "server test step") + case .compareFile: return NSLocalizedString("Compare file", comment: "server test step") + case .deleteFile: return NSLocalizedString("Delete file", comment: "server test step") + } + } +} + +struct ProtocolTestFailure: Decodable, Error, Equatable { + var testStep: ProtocolTestStep + var testError: AgentErrorType + + static func == (l: ProtocolTestFailure, r: ProtocolTestFailure) -> Bool { + l.testStep == r.testStep + } + + var localizedDescription: String { + let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text) + switch testError { + case .SMP(_, .AUTH): + return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error") + case .XFTP(.AUTH): + return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error") + case .BROKER(_, .NETWORK): + return err + " " + NSLocalizedString("Possibly, certificate fingerprint in server address is incorrect", comment: "server test error") + default: + return err + } + } +} + +struct MigrationFileLinkData: Codable { + let networkConfig: NetworkConfig? + + struct NetworkConfig: Codable { + let socksProxy: String? + let networkProxy: NetworkProxy? + let hostMode: HostMode? + let requiredHostMode: Bool? + + func transformToPlatformSupported() -> NetworkConfig { + return if let hostMode, let requiredHostMode { + NetworkConfig( + socksProxy: nil, + networkProxy: nil, + hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, + requiredHostMode: requiredHostMode + ) + } else { self } + } + } + + func addToLink(link: String) -> String { + "\(link)&data=\(encodeJSON(self).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)" + } + + static func readFromLink(link: String) -> MigrationFileLinkData? { +// standaloneFileInfo(link) + nil + } +} + +struct AppSettings: Codable, Equatable { + var networkConfig: NetCfg? = nil + var networkProxy: NetworkProxy? = nil + var privacyEncryptLocalFiles: Bool? = nil + var privacyAskToApproveRelays: Bool? = nil + var privacyAcceptImages: Bool? = nil + var privacyLinkPreviews: Bool? = nil + var privacyShowChatPreviews: Bool? = nil + var privacySaveLastDraft: Bool? = nil + var privacyProtectScreen: Bool? = nil + var privacyMediaBlurRadius: Int? = nil + var notificationMode: AppSettingsNotificationMode? = nil + var notificationPreviewMode: NotificationPreviewMode? = nil + var webrtcPolicyRelay: Bool? = nil + var webrtcICEServers: [String]? = nil + var confirmRemoteSessions: Bool? = nil + var connectRemoteViaMulticast: Bool? = nil + var connectRemoteViaMulticastAuto: Bool? = nil + var developerTools: Bool? = nil + var confirmDBUpgrades: Bool? = nil + var androidCallOnLockScreen: AppSettingsLockScreenCalls? = nil + var iosCallKitEnabled: Bool? = nil + var iosCallKitCallsInRecents: Bool? = nil + var uiProfileImageCornerRadius: Double? = nil + var uiChatItemRoundness: Double? = nil + var uiChatItemTail: Bool? = nil + var uiColorScheme: String? = nil + var uiDarkColorScheme: String? = nil + var uiCurrentThemeIds: [String: String]? = nil + var uiThemes: [ThemeOverrides]? = nil + var oneHandUI: Bool? = nil + var chatBottomBar: Bool? = nil + + func prepareForExport() -> AppSettings { + var empty = AppSettings() + let def = AppSettings.defaults + if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } + if networkProxy != def.networkProxy { empty.networkProxy = networkProxy } + if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } + if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } + if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } + if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } + if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } + if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } + if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } + if privacyMediaBlurRadius != def.privacyMediaBlurRadius { empty.privacyMediaBlurRadius = privacyMediaBlurRadius } + if notificationMode != def.notificationMode { empty.notificationMode = notificationMode } + if notificationPreviewMode != def.notificationPreviewMode { empty.notificationPreviewMode = notificationPreviewMode } + if webrtcPolicyRelay != def.webrtcPolicyRelay { empty.webrtcPolicyRelay = webrtcPolicyRelay } + if webrtcICEServers != def.webrtcICEServers { empty.webrtcICEServers = webrtcICEServers } + if confirmRemoteSessions != def.confirmRemoteSessions { empty.confirmRemoteSessions = confirmRemoteSessions } + if connectRemoteViaMulticast != def.connectRemoteViaMulticast {empty.connectRemoteViaMulticast = connectRemoteViaMulticast } + if connectRemoteViaMulticastAuto != def.connectRemoteViaMulticastAuto { empty.connectRemoteViaMulticastAuto = connectRemoteViaMulticastAuto } + if developerTools != def.developerTools { empty.developerTools = developerTools } + if confirmDBUpgrades != def.confirmDBUpgrades { empty.confirmDBUpgrades = confirmDBUpgrades } + if androidCallOnLockScreen != def.androidCallOnLockScreen { empty.androidCallOnLockScreen = androidCallOnLockScreen } + if iosCallKitEnabled != def.iosCallKitEnabled { empty.iosCallKitEnabled = iosCallKitEnabled } + if iosCallKitCallsInRecents != def.iosCallKitCallsInRecents { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } + if uiProfileImageCornerRadius != def.uiProfileImageCornerRadius { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius } + if uiChatItemRoundness != def.uiChatItemRoundness { empty.uiChatItemRoundness = uiChatItemRoundness } + if uiChatItemTail != def.uiChatItemTail { empty.uiChatItemTail = uiChatItemTail } + if uiColorScheme != def.uiColorScheme { empty.uiColorScheme = uiColorScheme } + if uiDarkColorScheme != def.uiDarkColorScheme { empty.uiDarkColorScheme = uiDarkColorScheme } + if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds } + if uiThemes != def.uiThemes { empty.uiThemes = uiThemes } + if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI } + if chatBottomBar != def.chatBottomBar { empty.chatBottomBar = chatBottomBar } + return empty + } + + static var defaults: AppSettings { + AppSettings ( + networkConfig: NetCfg.defaults, + networkProxy: NetworkProxy.def, + privacyEncryptLocalFiles: true, + privacyAskToApproveRelays: true, + privacyAcceptImages: true, + privacyLinkPreviews: true, + privacyShowChatPreviews: true, + privacySaveLastDraft: true, + privacyProtectScreen: false, + privacyMediaBlurRadius: 0, + notificationMode: AppSettingsNotificationMode.instant, + notificationPreviewMode: NotificationPreviewMode.message, + webrtcPolicyRelay: true, + webrtcICEServers: [], + confirmRemoteSessions: false, + connectRemoteViaMulticast: true, + connectRemoteViaMulticastAuto: true, + developerTools: false, + confirmDBUpgrades: false, + androidCallOnLockScreen: AppSettingsLockScreenCalls.show, + iosCallKitEnabled: true, + iosCallKitCallsInRecents: false, + uiProfileImageCornerRadius: 22.5, + uiChatItemRoundness: 0.75, + uiChatItemTail: true, + uiColorScheme: DefaultTheme.SYSTEM_THEME_NAME, + uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, + uiCurrentThemeIds: nil as [String: String]?, + uiThemes: nil as [ThemeOverrides]?, + oneHandUI: true, + chatBottomBar: true + ) + } +} + +enum AppSettingsNotificationMode: String, Codable { + case off + case periodic + case instant + + func toNotificationsMode() -> NotificationsMode { + switch self { + case .instant: .instant + case .periodic: .periodic + case .off: .off + } + } + + static func from(_ mode: NotificationsMode) -> AppSettingsNotificationMode { + switch mode { + case .instant: .instant + case .periodic: .periodic + case .off: .off + } + } +} + +//enum NotificationPreviewMode: Codable { +// case hidden +// case contact +// case message +//} + +enum AppSettingsLockScreenCalls: String, Codable { + case disable + case show + case accept +} + +struct UserNetworkInfo: Codable, Equatable { + let networkType: UserNetworkType + let online: Bool +} + +enum UserNetworkType: String, Codable { + case none + case cellular + case wifi + case ethernet + case other + + var text: LocalizedStringKey { + switch self { + case .none: "No network connection" + case .cellular: "Cellular" + case .wifi: "WiFi" + case .ethernet: "Wired ethernet" + case .other: "Other" + } + } +} + +struct RcvMsgInfo: Codable { + var msgId: Int64 + var msgDeliveryId: Int64 + var msgDeliveryStatus: String + var agentMsgId: Int64 + var agentMsgMeta: String +} + +struct ServerQueueInfo: Codable { + var server: String + var rcvId: String + var sndId: String + var ntfId: String? + var status: String + var info: QueueInfo +} + +struct QueueInfo: Codable { + var qiSnd: Bool + var qiNtf: Bool + var qiSub: QSub? + var qiSize: Int + var qiMsg: MsgInfo? +} + +struct QSub: Codable { + var qSubThread: QSubThread + var qDelivered: String? +} + +enum QSubThread: String, Codable { + case noSub + case subPending + case subThread + case prohibitSub +} + +struct MsgInfo: Codable { + var msgId: String + var msgTs: Date + var msgType: MsgType +} + +enum MsgType: String, Codable { + case message + case quota +} + +struct PresentedServersSummary: Codable { + var statsStartedAt: Date + var allUsersSMP: SMPServersSummary + var allUsersXFTP: XFTPServersSummary + var currentUserSMP: SMPServersSummary + var currentUserXFTP: XFTPServersSummary +} + +struct SMPServersSummary: Codable { + var smpTotals: SMPTotals + var currentlyUsedSMPServers: [SMPServerSummary] + var previouslyUsedSMPServers: [SMPServerSummary] + var onlyProxiedSMPServers: [SMPServerSummary] +} + +struct SMPTotals: Codable { + var sessions: ServerSessions + var subs: SMPServerSubs + var stats: AgentSMPServerStatsData +} + +struct SMPServerSummary: Codable, Identifiable { + var smpServer: String + var known: Bool? + var sessions: ServerSessions? + var subs: SMPServerSubs? + var stats: AgentSMPServerStatsData? + + var id: String { smpServer } + + var hasSubs: Bool { subs != nil } + + var sessionsOrNew: ServerSessions { sessions ?? ServerSessions.newServerSessions } + + var subsOrNew: SMPServerSubs { subs ?? SMPServerSubs.newSMPServerSubs } +} + +struct ServerSessions: Codable { + var ssConnected: Int + var ssErrors: Int + var ssConnecting: Int + + static var newServerSessions = ServerSessions( + ssConnected: 0, + ssErrors: 0, + ssConnecting: 0 + ) + + var hasSess: Bool { ssConnected > 0 } +} + +struct SMPServerSubs: Codable { + var ssActive: Int + var ssPending: Int + + static var newSMPServerSubs = SMPServerSubs( + ssActive: 0, + ssPending: 0 + ) + + var total: Int { ssActive + ssPending } + + var shareOfActive: Double { + guard total != 0 else { return 0.0 } + return Double(ssActive) / Double(total) + } +} + +struct AgentSMPServerStatsData: Codable { + var _sentDirect: Int + var _sentViaProxy: Int + var _sentProxied: Int + var _sentDirectAttempts: Int + var _sentViaProxyAttempts: Int + var _sentProxiedAttempts: Int + var _sentAuthErrs: Int + var _sentQuotaErrs: Int + var _sentExpiredErrs: Int + var _sentOtherErrs: Int + var _recvMsgs: Int + var _recvDuplicates: Int + var _recvCryptoErrs: Int + var _recvErrs: Int + var _ackMsgs: Int + var _ackAttempts: Int + var _ackNoMsgErrs: Int + var _ackOtherErrs: Int + var _connCreated: Int + var _connSecured: Int + var _connCompleted: Int + var _connDeleted: Int + var _connDelAttempts: Int + var _connDelErrs: Int + var _connSubscribed: Int + var _connSubAttempts: Int + var _connSubIgnored: Int + var _connSubErrs: Int + var _ntfKey: Int + var _ntfKeyAttempts: Int + var _ntfKeyDeleted: Int + var _ntfKeyDeleteAttempts: Int +} + +struct XFTPServersSummary: Codable { + var xftpTotals: XFTPTotals + var currentlyUsedXFTPServers: [XFTPServerSummary] + var previouslyUsedXFTPServers: [XFTPServerSummary] +} + +struct XFTPTotals: Codable { + var sessions: ServerSessions + var stats: AgentXFTPServerStatsData +} + +struct XFTPServerSummary: Codable, Identifiable { + var xftpServer: String + var known: Bool? + var sessions: ServerSessions? + var stats: AgentXFTPServerStatsData? + var rcvInProgress: Bool + var sndInProgress: Bool + var delInProgress: Bool + + var id: String { xftpServer } +} + +struct AgentXFTPServerStatsData: Codable { + var _uploads: Int + var _uploadsSize: Int64 + var _uploadAttempts: Int + var _uploadErrs: Int + var _downloads: Int + var _downloadsSize: Int64 + var _downloadAttempts: Int + var _downloadAuthErrs: Int + var _downloadErrs: Int + var _deletions: Int + var _deleteAttempts: Int + var _deleteErrs: Int +} + +struct AgentNtfServerStatsData: Codable { + var _ntfCreated: Int + var _ntfCreateAttempts: Int + var _ntfChecked: Int + var _ntfCheckAttempts: Int + var _ntfDeleted: Int + var _ntfDelAttempts: Int +} diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 0ac1a9cacb..9b9fda0397 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -30,9 +30,18 @@ actor TerminalItems { } } - func addCommand(_ start: Date, _ cmd: ChatCommand, _ resp: ChatResponse) async { + func addCommand(_ start: Date, _ cmd: ChatCommand, _ res: APIResult) async { await add(.cmd(start, cmd)) - await add(.resp(.now, resp)) + await addResult(res) + } + + func addResult(_ res: APIResult) async { + let item: TerminalItem = switch res { + case let .result(r): .res(.now, r) + case let .error(e): .err(.now, e) + case let .invalid(type, json): .bad(.now, type, json) + } + await add(item) } } @@ -53,51 +62,172 @@ class ItemsModel: ObservableObject { var itemAdded = false { willSet { publisher.send() } } - + + let chatState = ActiveChatState() + // Publishes directly to `objectWillChange` publisher, // this will cause reversedChatItems to be rendered without throttling @Published var isLoading = false - @Published var showLoadingProgress = false + @Published var showLoadingProgress: ChatId? = nil + + private var navigationTimeoutTask: Task? = nil + private var loadChatTask: Task? = nil + + var lastItemsLoaded: Bool { + chatState.splits.isEmpty || chatState.splits.first != reversedChatItems.first?.id + } init() { publisher - .throttle(for: 0.25, scheduler: DispatchQueue.main, latest: true) + .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) .sink { self.objectWillChange.send() } .store(in: &bag) } func loadOpenChat(_ chatId: ChatId, willNavigate: @escaping () -> Void = {}) { - let navigationTimeout = Task { + navigationTimeoutTask?.cancel() + loadChatTask?.cancel() + navigationTimeoutTask = Task { do { try await Task.sleep(nanoseconds: 250_000000) await MainActor.run { - willNavigate() ChatModel.shared.chatId = chatId + willNavigate() } } catch {} } - let progressTimeout = Task { - do { - try await Task.sleep(nanoseconds: 1500_000000) - await MainActor.run { showLoadingProgress = true } - } catch {} - } - Task { - if let chat = ChatModel.shared.getChat(chatId) { - await MainActor.run { self.isLoading = true } -// try? await Task.sleep(nanoseconds: 5000_000000) - await loadChat(chat: chat) - navigationTimeout.cancel() - progressTimeout.cancel() + loadChatTask = Task { + await MainActor.run { self.isLoading = true } +// try? await Task.sleep(nanoseconds: 1000_000000) + await loadChat(chatId: chatId) + if !Task.isCancelled { await MainActor.run { self.isLoading = false - self.showLoadingProgress = false - willNavigate() - ChatModel.shared.chatId = chatId + self.showLoadingProgress = nil } } } } + + func loadOpenChatNoWait(_ chatId: ChatId, _ openAroundItemId: ChatItem.ID? = nil) { + navigationTimeoutTask?.cancel() + loadChatTask?.cancel() + loadChatTask = Task { + // try? await Task.sleep(nanoseconds: 1000_000000) + await loadChat(chatId: chatId, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil) + if !Task.isCancelled { + await MainActor.run { + if openAroundItemId == nil { + ChatModel.shared.chatId = chatId + } + } + } + } + } +} + +class ChatTagsModel: ObservableObject { + static let shared = ChatTagsModel() + + @Published var userTags: [ChatTag] = [] + @Published var activeFilter: ActiveFilter? = nil + @Published var presetTags: [PresetTag:Int] = [:] + @Published var unreadTags: [Int64:Int] = [:] + + func updateChatTags(_ chats: [Chat]) { + let tm = ChatTagsModel.shared + var newPresetTags: [PresetTag:Int] = [:] + var newUnreadTags: [Int64:Int] = [:] + for chat in chats { + for tag in PresetTag.allCases { + if presetTagMatchesChat(tag, chat.chatInfo, chat.chatStats) { + newPresetTags[tag] = (newPresetTags[tag] ?? 0) + 1 + } + } + if chat.unreadTag, let tags = chat.chatInfo.chatTags { + for tag in tags { + newUnreadTags[tag] = (newUnreadTags[tag] ?? 0) + 1 + } + } + } + presetTags = newPresetTags + unreadTags = newUnreadTags + clearActiveChatFilterIfNeeded() + } + + func updateChatFavorite(favorite: Bool, wasFavorite: Bool) { + let count = presetTags[.favorites] + if favorite && !wasFavorite { + presetTags[.favorites] = (count ?? 0) + 1 + } else if !favorite && wasFavorite, let count { + presetTags[.favorites] = max(0, count - 1) + clearActiveChatFilterIfNeeded() + } + } + + func addPresetChatTags(_ chatInfo: ChatInfo, _ chatStats: ChatStats) { + for tag in PresetTag.allCases { + if presetTagMatchesChat(tag, chatInfo, chatStats) { + presetTags[tag] = (presetTags[tag] ?? 0) + 1 + } + } + } + + func removePresetChatTags(_ chatInfo: ChatInfo, _ chatStats: ChatStats) { + for tag in PresetTag.allCases { + if presetTagMatchesChat(tag, chatInfo, chatStats) { + if let count = presetTags[tag] { + if count > 1 { + presetTags[tag] = count - 1 + } else { + presetTags.removeValue(forKey: tag) + } + } + } + } + clearActiveChatFilterIfNeeded() + } + + func markChatTagRead(_ chat: Chat) -> Void { + if chat.unreadTag, let tags = chat.chatInfo.chatTags { + decTagsReadCount(tags) + } + } + + func updateChatTagRead(_ chat: Chat, wasUnread: Bool) -> Void { + guard let tags = chat.chatInfo.chatTags else { return } + let nowUnread = chat.unreadTag + if nowUnread && !wasUnread { + for tag in tags { + unreadTags[tag] = (unreadTags[tag] ?? 0) + 1 + } + } else if !nowUnread && wasUnread { + decTagsReadCount(tags) + } + } + + func decTagsReadCount(_ tags: [Int64]) -> Void { + for tag in tags { + if let count = unreadTags[tag] { + unreadTags[tag] = max(0, count - 1) + } + } + } + + func changeGroupReportsTag(_ by: Int = 0) { + if by == 0 { return } + presetTags[.groupReports] = max(0, (presetTags[.groupReports] ?? 0) + by) + clearActiveChatFilterIfNeeded() + } + + func clearActiveChatFilterIfNeeded() { + let clear = switch activeFilter { + case let .presetTag(tag): (presetTags[tag] ?? 0) == 0 + case let .userTag(tag): !userTags.contains(tag) + case .unread, nil: false + } + if clear { activeFilter = nil } + } } class NetworkModel: ObservableObject { @@ -123,6 +253,14 @@ class NetworkModel: ObservableObject { } } +/// ChatItemWithMenu can depend on previous or next item for it's appearance +/// This dummy model is used to force an update of all chat items, +/// when they might have changed appearance. +class ChatItemDummyModel: ObservableObject { + static let shared = ChatItemDummyModel() + func sendUpdate() { objectWillChange.send() } +} + final class ChatModel: ObservableObject { @Published var onboardingStage: OnboardingStage? @Published var setDeliveryReceipts = false @@ -139,6 +277,7 @@ final class ChatModel: ObservableObject { @Published var chatDbEncrypted: Bool? @Published var chatDbStatus: DBMigrationResult? @Published var ctrlInitInProgress: Bool = false + @Published var notificationResponse: UNNotificationResponse? // local authentication @Published var contentViewAccessAuthenticated: Bool = false @Published var laRequest: LocalAuthRequest? @@ -147,6 +286,7 @@ final class ChatModel: ObservableObject { @Published var deletedChats: Set = [] // current chat @Published var chatId: String? + @Published var openAroundItemId: ChatItem.ID? = nil var chatItemStatuses: Dictionary = [:] @Published var chatToTop: String? @Published var groupMembers: [GMember] = [] @@ -161,6 +301,7 @@ final class ChatModel: ObservableObject { @Published var deviceToken: DeviceToken? @Published var savedToken: DeviceToken? @Published var tokenRegistered = false + @Published var reRegisterTknStatus: NtfTknStatus? = nil @Published var tokenStatus: NtfTknStatus? @Published var notificationMode = NotificationsMode.off @Published var notificationServer: String? @@ -183,8 +324,9 @@ final class ChatModel: ObservableObject { @Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source @Published var draft: ComposeState? @Published var draftChatId: String? - @Published var pasteboardHasStrings: Bool = UIPasteboard.general.hasStrings @Published var networkInfo = UserNetworkInfo(networkType: .other, online: true) + // usage conditions + @Published var conditions: ServerOperatorConditions = .empty var messageDelivery: Dictionary Void> = [:] @@ -334,6 +476,7 @@ final class ChatModel: ObservableObject { updateChatInfo(cInfo) } else if addMissing { addChat(Chat(chatInfo: cInfo, chatItems: [])) + ChatTagsModel.shared.addPresetChatTags(cInfo, ChatStats()) } } @@ -391,7 +534,7 @@ final class ChatModel: ObservableObject { [cItem] } if case .rcvNew = cItem.meta.itemStatus { - unreadCollector.changeUnreadCounter(cInfo.id, by: 1) + unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0) } popChatCollector.throttlePopChat(cInfo.id, currentPosition: i) } else { @@ -428,19 +571,18 @@ final class ChatModel: ObservableObject { private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { if let i = getChatItemIndex(cItem) { - withConditionalAnimation { - _updateChatItem(at: i, with: cItem) - } + _updateChatItem(at: i, with: cItem) + ChatItemDummyModel.shared.sendUpdate() return false } else { - withConditionalAnimation(itemAnimation()) { - var ci = cItem - if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { - ci.meta.itemStatus = status - } - im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) - im.itemAdded = true + var ci = cItem + if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { + ci.meta.itemStatus = status } + im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) + im.chatState.itemAdded((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0) + im.itemAdded = true + ChatItemDummyModel.shared.sendUpdate() return true } @@ -473,7 +615,7 @@ final class ChatModel: ObservableObject { func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) { if cItem.isRcvNew { - unreadCollector.changeUnreadCounter(cInfo.id, by: -1) + unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0) } // update previews if let chat = getChat(cInfo.id) { @@ -484,14 +626,54 @@ final class ChatModel: ObservableObject { // remove from current chat if chatId == cInfo.id { if let i = getChatItemIndex(cItem) { - _ = withAnimation { - im.reversedChatItems.remove(at: i) + withAnimation { + let item = im.reversedChatItems.remove(at: i) + im.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed()) } } } VoiceItemState.stopVoiceInChatView(cInfo, cItem) } + func removeMemberItems(_ removedMember: GroupMember, byMember: GroupMember, _ groupInfo: GroupInfo) { + // this should not happen, only another member can "remove" user, user can only "leave" (another event). + if byMember.groupMemberId == groupInfo.membership.groupMemberId { + logger.debug("exiting removeMemberItems") + return + } + if chatId == groupInfo.id { + for i in 0.. 0, + let updatedItem = removedUpdatedItem(chat.chatItems[0]) { + chat.chatItems = [updatedItem] + } + + func removedUpdatedItem(_ item: ChatItem) -> ChatItem? { + let newContent: CIContent + if case .groupSnd = item.chatDir, removedMember.groupMemberId == groupInfo.membership.groupMemberId { + newContent = .sndModerated + } else if case let .groupRcv(groupMember) = item.chatDir, groupMember.groupMemberId == removedMember.groupMemberId { + newContent = .rcvModerated + } else { + return nil + } + var updatedItem = item + updatedItem.meta.itemDeleted = .moderated(deletedTs: Date.now, byGroupMember: byMember) + if groupInfo.fullGroupPreferences.fullDelete.on { + updatedItem.content = newContent + } + if item.isActiveReport { + decreaseGroupReportsCounter(groupInfo.id) + } + return updatedItem + } + } + func nextChatItemData(_ chatItemId: Int64, previous: Bool, map: @escaping (ChatItem) -> T?) -> T? { guard var i = im.reversedChatItems.firstIndex(where: { $0.id == chatItemId }) else { return nil } if previous { @@ -521,7 +703,7 @@ final class ChatModel: ObservableObject { } func updateCurrentUserUiThemes(uiThemes: ThemeModeOverrides?) { - guard var current = currentUser else { return } + guard var current = currentUser, current.uiThemes != uiThemes else { return } current.uiThemes = uiThemes let i = users.firstIndex(where: { $0.user.userId == current.userId }) if let i { @@ -534,6 +716,7 @@ final class ChatModel: ObservableObject { let cItem = ChatItem.liveDummy(chatInfo.chatType) withAnimation { im.reversedChatItems.insert(cItem, at: 0) + im.chatState.itemAdded((cItem.id, cItem.isRcvNew), 0) im.itemAdded = true } return cItem @@ -553,63 +736,37 @@ final class ChatModel: ObservableObject { im.reversedChatItems.first?.isLiveDummy == true } - func markChatItemsRead(_ cInfo: ChatInfo) { + func markAllChatItemsRead(_ cInfo: ChatInfo) { // update preview _updateChat(cInfo.id) { chat in - self.decreaseUnreadCounter(user: self.currentUser!, by: chat.chatStats.unreadCount) + self.decreaseUnreadCounter(user: self.currentUser!, chat: chat) + ChatTagsModel.shared.markChatTagRead(chat) chat.chatStats = ChatStats() } // update current chat if chatId == cInfo.id { - markCurrentChatRead() - } - } - - private func markCurrentChatRead(fromIndex i: Int = 0) { - var j = i - while j < im.reversedChatItems.count { - markChatItemRead_(j) - j += 1 - } - } - - func markChatItemsRead(_ cInfo: ChatInfo, aboveItem: ChatItem? = nil) { - if let cItem = aboveItem { - if chatId == cInfo.id, let i = getChatItemIndex(cItem) { - markCurrentChatRead(fromIndex: i) - _updateChat(cInfo.id) { chat in - var unreadBelow = 0 - var j = i - 1 - while j >= 0 { - if case .rcvNew = self.im.reversedChatItems[j].meta.itemStatus { - unreadBelow += 1 - } - j -= 1 - } - // update preview - let markedCount = chat.chatStats.unreadCount - unreadBelow - if markedCount > 0 { - chat.chatStats.unreadCount -= markedCount - self.decreaseUnreadCounter(user: self.currentUser!, by: markedCount) - } - } + var i = 0 + while i < im.reversedChatItems.count { + markChatItemRead_(i) + i += 1 } - } else { - markChatItemsRead(cInfo) + im.chatState.itemsRead(nil, im.reversedChatItems.reversed()) } } - func markChatUnread(_ cInfo: ChatInfo, unreadChat: Bool = true) { _updateChat(cInfo.id) { chat in + let wasUnread = chat.unreadTag chat.chatStats.unreadChat = unreadChat + ChatTagsModel.shared.updateChatTagRead(chat, wasUnread: wasUnread) } } func clearChat(_ cInfo: ChatInfo) { // clear preview if let chat = getChat(cInfo.id) { - self.decreaseUnreadCounter(user: self.currentUser!, by: chat.chatStats.unreadCount) + self.decreaseUnreadCounter(user: self.currentUser!, chat: chat) chat.chatItems = [] + ChatTagsModel.shared.markChatTagRead(chat) chat.chatStats = ChatStats() chat.chatInfo = cInfo } @@ -617,22 +774,27 @@ final class ChatModel: ObservableObject { if chatId == cInfo.id { chatItemStatuses = [:] im.reversedChatItems = [] + im.chatState.clear() } } - func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { - if chatId == cInfo.id, - let itemIndex = getChatItemIndex(cItem), - im.reversedChatItems[itemIndex].isRcvNew { - await MainActor.run { - withTransaction(Transaction()) { - // update current chat - markChatItemRead_(itemIndex) - // update preview - unreadCollector.changeUnreadCounter(cInfo.id, by: -1) + func markChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], _ mentionsRead: Int) { + if self.chatId == cInfo.id { + var unreadItemIds: Set = [] + var i = 0 + var ids = Set(itemIds) + while i < im.reversedChatItems.count && !ids.isEmpty { + let item = im.reversedChatItems[i] + if ids.contains(item.id) && item.isRcvNew { + markChatItemRead_(i) + unreadItemIds.insert(item.id) + ids.remove(item.id) } + i += 1 } + im.chatState.itemsRead(unreadItemIds, im.reversedChatItems.reversed()) } + self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count, unreadMentions: -mentionsRead) } private let unreadCollector = UnreadCollector() @@ -640,16 +802,16 @@ final class ChatModel: ObservableObject { class UnreadCollector { private let subject = PassthroughSubject() private var bag = Set() - private var unreadCounts: [ChatId: Int] = [:] + private var unreadCounts: [ChatId: (unread: Int, mentions: Int)] = [:] init() { subject .debounce(for: 1, scheduler: DispatchQueue.main) .sink { let m = ChatModel.shared - for (chatId, count) in self.unreadCounts { - if let i = m.getChatIndex(chatId) { - m.changeUnreadCounter(i, by: count) + for (chatId, (unread, mentions)) in self.unreadCounts { + if unread != 0 || mentions != 0, let i = m.getChatIndex(chatId) { + m.changeUnreadCounter(i, by: unread, unreadMentions: mentions) } } self.unreadCounts = [:] @@ -657,10 +819,9 @@ final class ChatModel: ObservableObject { .store(in: &bag) } - func changeUnreadCounter(_ chatId: ChatId, by count: Int) { - DispatchQueue.main.async { - self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count - } + func changeUnreadCounter(_ chatId: ChatId, by count: Int, unreadMentions: Int) { + let (unread, mentions) = self.unreadCounts[chatId] ?? (0, 0) + self.unreadCounts[chatId] = (unread + count, mentions + unreadMentions) subject.send() } } @@ -738,8 +899,12 @@ final class ChatModel: ObservableObject { } } - func changeUnreadCounter(_ chatIndex: Int, by count: Int) { - chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount + count + func changeUnreadCounter(_ chatIndex: Int, by count: Int, unreadMentions: Int) { + let wasUnread = chats[chatIndex].unreadTag + let stats = chats[chatIndex].chatStats + chats[chatIndex].chatStats.unreadCount = stats.unreadCount + count + chats[chatIndex].chatStats.unreadMentions = stats.unreadMentions + unreadMentions + ChatTagsModel.shared.updateChatTagRead(chats[chatIndex], wasUnread: wasUnread) changeUnreadCounter(user: currentUser!, by: count) } @@ -747,6 +912,13 @@ final class ChatModel: ObservableObject { changeUnreadCounter(user: user, by: 1) } + func decreaseUnreadCounter(user: any UserLike, chat: Chat) { + let by = chat.chatInfo.chatSettings?.enableNtfs == .mentions + ? chat.chatStats.unreadMentions + : chat.chatStats.unreadCount + decreaseUnreadCounter(user: user, by: by) + } + func decreaseUnreadCounter(user: any UserLike, by: Int = 1) { changeUnreadCounter(user: user, by: -by) } @@ -759,8 +931,41 @@ final class ChatModel: ObservableObject { } func totalUnreadCountForAllUsers() -> Int { - chats.filter { $0.chatInfo.ntfsEnabled }.reduce(0, { count, chat in count + chat.chatStats.unreadCount }) + - users.filter { !$0.user.activeUser }.reduce(0, { unread, next -> Int in unread + next.unreadCount }) + var unread: Int = 0 + for chat in chats { + switch chat.chatInfo.chatSettings?.enableNtfs { + case .all: unread += chat.chatStats.unreadCount + case .mentions: unread += chat.chatStats.unreadMentions + default: () + } + } + for u in users { + if !u.user.activeUser { + unread += u.unreadCount + } + } + return unread + } + + func increaseGroupReportsCounter(_ chatId: ChatId) { + changeGroupReportsCounter(chatId, 1) + } + + func decreaseGroupReportsCounter(_ chatId: ChatId, by: Int = 1) { + changeGroupReportsCounter(chatId, -by) + } + + private func changeGroupReportsCounter(_ chatId: ChatId, _ by: Int = 0) { + if by == 0 { return } + + if let i = getChatIndex(chatId) { + let chat = chats[i] + let wasReportsCount = chat.chatStats.reportsCount + chat.chatStats.reportsCount = max(0, chat.chatStats.reportsCount + by) + let nowReportsCount = chat.chatStats.reportsCount + let by = wasReportsCount == 0 && nowReportsCount > 0 ? 1 : (wasReportsCount > 0 && nowReportsCount == 0) ? -1 : 0 + ChatTagsModel.shared.changeGroupReportsTag(by) + } } // this function analyses "connected" events and assumes that each member will be there only once @@ -808,12 +1013,17 @@ final class ChatModel: ObservableObject { // returns the previous member in the same merge group and the count of members in this group func getPrevHiddenMember(_ member: GroupMember, _ range: ClosedRange) -> (GroupMember?, Int) { + let items = im.reversedChatItems var prevMember: GroupMember? = nil var memberIds: Set = [] for i in range { - if case let .groupRcv(m) = im.reversedChatItems[i].chatDir { - if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m } - memberIds.insert(m.groupMemberId) + if i < items.count { + if case let .groupRcv(m) = items[i].chatDir { + if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m } + memberIds.insert(m.groupMemberId) + } + } else { + logger.error("getPrevHiddenMember: index >= count of reversed items: \(i) vs \(items.count), range: \(String(describing: range))") } } return (prevMember, memberIds.count) @@ -832,7 +1042,7 @@ final class ChatModel: ObservableObject { } func dismissConnReqView(_ id: String) { - if id == showingInvitation?.connId { + if id == showingInvitation?.pcc.id { markShowingInvitationUsed() dismissAllSheets() } @@ -844,7 +1054,11 @@ final class ChatModel: ObservableObject { func removeChat(_ id: String) { withAnimation { - chats.removeAll(where: { $0.id == id }) + if let i = getChatIndex(id) { + let removed = chats.remove(at: i) + ChatTagsModel.shared.removePresetChatTags(removed.chatInfo, removed.chatStats) + removeWallpaperFilesFromChat(removed) + } } } @@ -883,38 +1097,26 @@ final class ChatModel: ObservableObject { } } - func unreadChatItemCounts(itemsInView: Set) -> UnreadChatItemCounts { - var i = 0 - var totalBelow = 0 - var unreadBelow = 0 - while i < im.reversedChatItems.count - 1 && !itemsInView.contains(im.reversedChatItems[i].viewId) { - totalBelow += 1 - if im.reversedChatItems[i].isRcvNew { - unreadBelow += 1 - } - i += 1 + func removeWallpaperFilesFromChat(_ chat: Chat) { + if case let .direct(contact) = chat.chatInfo { + removeWallpaperFilesFromTheme(contact.uiThemes) + } else if case let .group(groupInfo) = chat.chatInfo { + removeWallpaperFilesFromTheme(groupInfo.uiThemes) } - return UnreadChatItemCounts( - // TODO these thresholds account for the fact that items are still "visible" while - // covered by compose area, they should be replaced with the actual height in pixels below the screen. - isNearBottom: totalBelow < 15, - isReallyNearBottom: totalBelow < 2, - unreadBelow: unreadBelow - ) } - func topItemInView(itemsInView: Set) -> ChatItem? { - let maxIx = im.reversedChatItems.count - 1 - var i = 0 - let inView = { itemsInView.contains(self.im.reversedChatItems[$0].viewId) } - while i < maxIx && !inView(i) { i += 1 } - while i < maxIx && inView(i) { i += 1 } - return im.reversedChatItems[min(i - 1, maxIx)] + func removeWallpaperFilesFromAllChats(_ user: User) { + // Currently, only removing everything from currently active user is supported. Inactive users are TODO + if user.userId == currentUser?.userId { + chats.forEach { + removeWallpaperFilesFromChat($0) + } + } } } struct ShowingInvitation { - var connId: String + var pcc: PendingContactConnection var connChatUsed: Bool } @@ -923,12 +1125,6 @@ struct NTFContactRequest { var chatId: String } -struct UnreadChatItemCounts: Equatable { - var isNearBottom: Bool - var isReallyNearBottom: Bool - var unreadBelow: Int -} - final class Chat: ObservableObject, Identifiable, ChatLike { @Published var chatInfo: ChatInfo @Published var chatItems: [ChatItem] @@ -956,27 +1152,14 @@ final class Chat: ObservableObject, Identifiable, ChatLike { ) } - var userCanSend: Bool { - switch chatInfo { - case .direct: return true - case let .group(groupInfo): - let m = groupInfo.membership - return m.memberActive && m.memberRole >= .member - case .local: - return true - default: return false + var unreadTag: Bool { + switch chatInfo.chatSettings?.enableNtfs { + case .all: chatStats.unreadChat || chatStats.unreadCount > 0 + case .mentions: chatStats.unreadChat || chatStats.unreadMentions > 0 + default: chatStats.unreadChat } } - - var userIsObserver: Bool { - switch chatInfo { - case let .group(groupInfo): - let m = groupInfo.membership - return m.memberActive && m.memberRole == .observer - default: return false - } - } - + var id: ChatId { get { chatInfo.id } } var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } } diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 95063845f1..da55bd90d0 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -26,20 +26,37 @@ enum NtfCallAction { class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { static let shared = NtfManager() + public var navigatingToChat = false private var granted = false private var prevNtfTime: Dictionary = [:] + override init() { + super.init() + UNUserNotificationCenter.current().delegate = self + } + // Handle notification when app is in background func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler handler: () -> Void) { logger.debug("NtfManager.userNotificationCenter: didReceive") - let content = response.notification.request.content + if appStateGroupDefault.get() == .active { + processNotificationResponse(response) + } else { + logger.debug("NtfManager.userNotificationCenter: remember response in model") + ChatModel.shared.notificationResponse = response + } + handler() + } + + func processNotificationResponse(_ ntfResponse: UNNotificationResponse) { let chatModel = ChatModel.shared - let action = response.actionIdentifier - logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") + let content = ntfResponse.notification.request.content + let action = ntfResponse.actionIdentifier + logger.debug("NtfManager.processNotificationResponse: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") if let userId = content.userInfo["userId"] as? Int64, userId != chatModel.currentUser?.userId { + logger.debug("NtfManager.processNotificationResponse changeActiveUser") changeActiveUser(userId, viewPwd: nil) } if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito), @@ -58,10 +75,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { } } else { if let chatId = content.targetContentIdentifier { - ItemsModel.shared.loadOpenChat(chatId) + self.navigatingToChat = true + ItemsModel.shared.loadOpenChat(chatId) { + self.navigatingToChat = false + } } } - handler() } private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? { @@ -76,7 +95,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { return nil } - // Handle notification when the app is in foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, @@ -185,6 +203,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: NSLocalizedString("SimpleX encrypted message or connection event", comment: "notification") + ), + UNNotificationCategory( + identifier: ntfCategoryManyEvents, + actions: [], + intentIdentifiers: [], + hiddenPreviewsBodyPlaceholder: NSLocalizedString("New events", comment: "notification") ) ]) } @@ -210,29 +234,28 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { } } } - center.delegate = self } func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) { logger.debug("NtfManager.notifyContactRequest") - addNotification(createContactRequestNtf(user, contactRequest)) + addNotification(createContactRequestNtf(user, contactRequest, 0)) } func notifyContactConnected(_ user: any UserLike, _ contact: Contact) { logger.debug("NtfManager.notifyContactConnected") - addNotification(createContactConnectedNtf(user, contact)) + addNotification(createContactConnectedNtf(user, contact, 0)) } func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) { logger.debug("NtfManager.notifyMessageReceived") - if cInfo.ntfsEnabled { - addNotification(createMessageReceivedNtf(user, cInfo, cItem)) + if cInfo.ntfsEnabled(chatItem: cItem) { + addNotification(createMessageReceivedNtf(user, cInfo, cItem, 0)) } } func notifyCallInvitation(_ invitation: RcvCallInvitation) { logger.debug("NtfManager.notifyCallInvitation") - addNotification(createCallInvitationNtf(invitation)) + addNotification(createCallInvitationNtf(invitation, 0)) } func setNtfBadgeCount(_ count: Int) { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ebc58c6a05..d92411decd 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -11,46 +11,42 @@ import UIKit import Dispatch import BackgroundTasks import SwiftUI -import SimpleXChat +@preconcurrency import SimpleXChat private var chatController: chat_ctrl? -// currentChatVersion in core -public let CURRENT_CHAT_VERSION: Int = 2 - -// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core) -public let CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion: 2, maxVersion: CURRENT_CHAT_VERSION) - private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network-statuses.lock") enum TerminalItem: Identifiable { case cmd(Date, ChatCommand) - case resp(Date, ChatResponse) + case res(Date, ChatAPIResult) + case err(Date, ChatError) + case bad(Date, String, Data?) var id: Date { - get { - switch self { - case let .cmd(id, _): return id - case let .resp(id, _): return id - } + switch self { + case let .cmd(d, _): d + case let .res(d, _): d + case let .err(d, _): d + case let .bad(d, _, _): d } } var label: String { - get { - switch self { - case let .cmd(_, cmd): return "> \(cmd.cmdString.prefix(30))" - case let .resp(_, resp): return "< \(resp.responseType)" - } + switch self { + case let .cmd(_, cmd): "> \(cmd.cmdString.prefix(30))" + case let .res(_, res): "< \(res.responseType)" + case let .err(_, err): "< error \(err.errorType)" + case let .bad(_, type, _): "< * \(type)" } } var details: String { - get { - switch self { - case let .cmd(_, cmd): return cmd.cmdString - case let .resp(_, resp): return resp.details - } + switch self { + case let .cmd(_, cmd): cmd.cmdString + case let .res(_, res): res.details + case let .err(_, err): String(describing: err) + case let .bad(_, _, json): dataToString(json) } } } @@ -92,18 +88,24 @@ private func withBGTask(bgDelay: Double? = nil, f: @escaping () -> T) -> T { return r } -func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) -> ChatResponse { +@inline(__always) +func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) throws -> R { + let res: APIResult = chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log) + return try apiResult(res) +} + +func chatApiSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) -> APIResult { if log { logger.debug("chatSendCmd \(cmd.cmdType)") } let start = Date.now - let resp = bgTask + let resp: APIResult = bgTask ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl) } : sendSimpleXCmd(cmd, ctrl) if log { logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") - if case let .response(_, json) = resp { - logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)") + if case let .invalid(_, json) = resp { + logger.debug("chatSendCmd \(cmd.cmdType) response: \(dataToString(json))") } Task { await TerminalItems.shared.addCommand(start, cmd.obfuscated, resp) @@ -112,35 +114,51 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = return resp } -func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) async -> ChatResponse { +@inline(__always) +func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async throws -> R { + let res: APIResult = await chatApiSendCmd(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log) + return try apiResult(res) +} + +@inline(__always) +func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async -> APIResult { await withCheckedContinuation { cont in - cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl, log: log)) + cont.resume(returning: chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)) } } -func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? { +@inline(__always) +func apiResult(_ res: APIResult) throws -> R { + switch res { + case let .result(r): return r + case let .error(e): throw e + case let .invalid(type, _): throw ChatError.unexpectedResult(type: type) + } +} + +func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> APIResult? { await withCheckedContinuation { cont in - _ = withBGTask(bgDelay: msgDelay) { () -> ChatResponse? in - let resp = recvSimpleXMsg(ctrl) - cont.resume(returning: resp) - return resp + _ = withBGTask(bgDelay: msgDelay) { () -> APIResult? in + let evt: APIResult? = recvSimpleXMsg(ctrl) + cont.resume(returning: evt) + return evt } } } func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? { - let r = chatSendCmdSync(.showActiveUser, ctrl) + let r: APIResult = chatApiSendCmdSync(.showActiveUser, ctrl: ctrl) switch r { - case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): return nil - default: throw r + case let .result(.activeUser(user)): return user + case .error(.error(.noActiveUser)): return nil + default: throw r.unexpected } } func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User { - let r = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl) + let r: ChatResponse0 = try chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl: ctrl) if case let .activeUser(user) = r { return user } - throw r + throw r.unexpected } func listUsers() throws -> [UserInfo] { @@ -151,41 +169,35 @@ func listUsersAsync() async throws -> [UserInfo] { return try listUsersResponse(await chatSendCmd(.listUsers)) } -private func listUsersResponse(_ r: ChatResponse) throws -> [UserInfo] { +private func listUsersResponse(_ r: ChatResponse0) throws -> [UserInfo] { if case let .usersList(users) = r { return users.sorted { $0.user.chatViewName.compare($1.user.chatViewName) == .orderedAscending } } - throw r + throw r.unexpected } func apiSetActiveUser(_ userId: Int64, viewPwd: String?) throws -> User { - let r = chatSendCmdSync(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) if case let .activeUser(user) = r { return user } - throw r + throw r.unexpected } func apiSetActiveUserAsync(_ userId: Int64, viewPwd: String?) async throws -> User { - let r = await chatSendCmd(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) + let r: ChatResponse0 = try await chatSendCmd(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) if case let .activeUser(user) = r { return user } - throw r + throw r.unexpected } func apiSetAllContactReceipts(enable: Bool) async throws { - let r = await chatSendCmd(.setAllContactReceipts(enable: enable)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.setAllContactReceipts(enable: enable)) } func apiSetUserContactReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws { - let r = await chatSendCmd(.apiSetUserContactReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiSetUserContactReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) } func apiSetUserGroupReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws { - let r = await chatSendCmd(.apiSetUserGroupReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiSetUserGroupReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) } func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User { @@ -205,90 +217,88 @@ func apiUnmuteUser(_ userId: Int64) async throws -> User { } func setUserPrivacy_(_ cmd: ChatCommand) async throws -> User { - let r = await chatSendCmd(cmd) + let r: ChatResponse1 = try await chatSendCmd(cmd) if case let .userPrivacy(_, updatedUser) = r { return updatedUser } - throw r + throw r.unexpected } func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) async throws { - let r = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd)) } func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool { - let r = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl) + let r: ChatResponse0 = try chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl: ctrl) switch r { case .chatStarted: return true case .chatRunning: return false - default: throw r + default: throw r.unexpected } } func apiCheckChatRunning() throws -> Bool { - let r = chatSendCmdSync(.checkChatRunning) + let r: ChatResponse0 = try chatSendCmdSync(.checkChatRunning) switch r { case .chatRunning: return true case .chatStopped: return false - default: throw r + default: throw r.unexpected } } func apiStopChat() async throws { - let r = await chatSendCmd(.apiStopChat) + let r: ChatResponse0 = try await chatSendCmd(.apiStopChat) switch r { case .chatStopped: return - default: throw r + default: throw r.unexpected } } func apiActivateChat() { chatReopenStore() - let r = chatSendCmdSync(.apiActivateChat(restoreChat: true)) - if case .cmdOk = r { return } - logger.error("apiActivateChat error: \(String(describing: r))") + do { + try sendCommandOkRespSync(.apiActivateChat(restoreChat: true)) + } catch { + logger.error("apiActivateChat error: \(responseError(error))") + } } func apiSuspendChat(timeoutMicroseconds: Int) { - let r = chatSendCmdSync(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) - if case .cmdOk = r { return } - logger.error("apiSuspendChat error: \(String(describing: r))") + do { + try sendCommandOkRespSync(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) + } catch { + logger.error("apiSuspendChat error: \(responseError(error))") + } } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, ctrl: chat_ctrl? = nil) throws { - let r = chatSendCmdSync(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder), ctrl) + let r: ChatResponse2 = try chatSendCmdSync(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder), ctrl: ctrl) if case .cmdOk = r { return } - throw r + throw r.unexpected } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable)) - if case .cmdOk = r { return } - throw r + try sendCommandOkRespSync(.apiSetEncryptLocalFiles(enable: enable)) } func apiSaveAppSettings(settings: AppSettings) throws { - let r = chatSendCmdSync(.apiSaveSettings(settings: settings)) - if case .cmdOk = r { return } - throw r + try sendCommandOkRespSync(.apiSaveSettings(settings: settings)) } func apiGetAppSettings(settings: AppSettings) throws -> AppSettings { - let r = chatSendCmdSync(.apiGetSettings(settings: settings)) + let r: ChatResponse2 = try chatSendCmdSync(.apiGetSettings(settings: settings)) if case let .appSettings(settings) = r { return settings } - throw r + throw r.unexpected } func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError] { - let r = await chatSendCmd(.apiExportArchive(config: config)) + let r: ChatResponse2 = try await chatSendCmd(.apiExportArchive(config: config)) if case let .archiveExported(archiveErrors) = r { return archiveErrors } - throw r + throw r.unexpected } func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError] { - let r = await chatSendCmd(.apiImportArchive(config: config)) + let r: ChatResponse2 = try await chatSendCmd(.apiImportArchive(config: config)) if case let .archiveImported(archiveErrors) = r { return archiveErrors } - throw r + throw r.unexpected } func apiDeleteStorage() async throws { @@ -299,8 +309,8 @@ func apiStorageEncryption(currentKey: String = "", newKey: String = "") async th try await sendCommandOkResp(.apiStorageEncryption(config: DBEncryptionConfig(currentKey: currentKey, newKey: newKey))) } -func testStorageEncryption(key: String, _ ctrl: chat_ctrl? = nil) async throws { - try await sendCommandOkResp(.testStorageEncryption(key: key), ctrl) +func testStorageEncryption(key: String, ctrl: chat_ctrl? = nil) async throws { + try await sendCommandOkResp(.testStorageEncryption(key: key), ctrl: ctrl) } func apiGetChats() throws -> [ChatData] { @@ -313,63 +323,103 @@ func apiGetChatsAsync() async throws -> [ChatData] { return try apiChatsResponse(await chatSendCmd(.apiGetChats(userId: userId))) } -private func apiChatsResponse(_ r: ChatResponse) throws -> [ChatData] { +private func apiChatsResponse(_ r: ChatResponse0) throws -> [ChatData] { if case let .apiChats(_, chats) = r { return chats } - throw r + throw r.unexpected +} + +func apiGetChatTags() throws -> [ChatTag] { + let userId = try currentUserId("apiGetChatTags") + let r: ChatResponse0 = try chatSendCmdSync(.apiGetChatTags(userId: userId)) + if case let .chatTags(_, tags) = r { return tags } + throw r.unexpected +} + +func apiGetChatTagsAsync() async throws -> [ChatTag] { + let userId = try currentUserId("apiGetChatTags") + let r: ChatResponse0 = try await chatSendCmd(.apiGetChatTags(userId: userId)) + if case let .chatTags(_, tags) = r { return tags } + throw r.unexpected } let loadItemsPerPage = 50 -func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> Chat { - let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: .last(count: loadItemsPerPage), search: search)) - if case let .apiChat(_, chat) = r { return Chat.init(chat) } - throw r -} - -func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> [ChatItem] { - let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination, search: search)) - if case let .apiChat(_, chat) = r { return chat.chatItems } - throw r +func apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) { + let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, pagination: pagination, search: search)) + if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo ?? NavigationInfo()) } + throw r.unexpected } func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async { - do { - let cInfo = chat.chatInfo - let m = ChatModel.shared - let im = ItemsModel.shared + await loadChat(chatId: chat.chatInfo.id, search: search, clearItems: clearItems) +} + +func loadChat(chatId: ChatId, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async { + let m = ChatModel.shared + let im = ItemsModel.shared + await MainActor.run { m.chatItemStatuses = [:] if clearItems { - await MainActor.run { im.reversedChatItems = [] } + im.reversedChatItems = [] + ItemsModel.shared.chatState.clear() } - let chat = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) - await MainActor.run { - im.reversedChatItems = chat.chatItems.reversed() - m.updateChatInfo(chat.chatInfo) - } - } catch let error { - logger.error("loadChat error: \(responseError(error))") } + await apiLoadMessages(chatId, openAroundItemId != nil ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage) : (search == "" ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)), im.chatState, search, openAroundItemId, { 0...0 }) } func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo { - let r = await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId)) if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo } - throw r + throw r.unexpected } -func apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) async -> ChatItem? { - let cmd: ChatCommand = .apiForwardChatItem(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemId: itemId, ttl: ttl) +func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) { + let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds)) + if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) } + throw r.unexpected +} + +func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { + let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) } -func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool = false, ttl: Int? = nil) async -> ChatItem? { - let cmd: ChatCommand = .apiSendMessage(type: type, id: id, file: file, quotedItemId: quotedItemId, msg: msg, live: live, ttl: ttl) +func apiCreateChatTag(tag: ChatTagData) async throws -> [ChatTag] { + let r: ChatResponse0 = try await chatSendCmd(.apiCreateChatTag(tag: tag)) + if case let .chatTags(_, userTags) = r { + return userTags + } + throw r.unexpected +} + +func apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64]) async throws -> ([ChatTag], [Int64]) { + let r: ChatResponse0 = try await chatSendCmd(.apiSetChatTags(type: type, id: id, tagIds: tagIds)) + if case let .tagsUpdated(_, userTags, chatTags) = r { + return (userTags, chatTags) + } + throw r.unexpected +} + +func apiDeleteChatTag(tagId: Int64) async throws { + try await sendCommandOkResp(.apiDeleteChatTag(tagId: tagId)) +} + +func apiUpdateChatTag(tagId: Int64, tag: ChatTagData) async throws { + try await sendCommandOkResp(.apiUpdateChatTag(tagId: tagId, tagData: tag)) +} + +func apiReorderChatTags(tagIds: [Int64]) async throws { + try await sendCommandOkResp(.apiReorderChatTags(tagIds: tagIds)) +} + +func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages) return await processSendMessageCmd(toChatType: type, cmd: cmd) } -private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> ChatItem? { +private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> [ChatItem]? { let chatModel = ChatModel.shared - let r: ChatResponse + let r: APIResult if toChatType == .direct { var cItem: ChatItem? = nil let endTask = beginBGTask({ @@ -379,37 +429,52 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async } } }) - r = await chatSendCmd(cmd, bgTask: false) - if case let .newChatItem(_, aChatItem) = r { - cItem = aChatItem.chatItem - chatModel.messageDelivery[aChatItem.chatItem.id] = endTask - return cItem + r = await chatApiSendCmd(cmd, bgTask: false) + if case let .result(.newChatItems(_, aChatItems)) = r { + let cItems = aChatItems.map { $0.chatItem } + if let cItemLast = cItems.last { + cItem = cItemLast + chatModel.messageDelivery[cItemLast.id] = endTask + } + return cItems } if let networkErrorAlert = networkErrorAlert(r) { AlertManager.shared.showAlert(networkErrorAlert) } else { - sendMessageErrorAlert(r) + sendMessageErrorAlert(r.unexpected) } endTask() return nil } else { - r = await chatSendCmd(cmd, bgDelay: msgDelay) - if case let .newChatItem(_, aChatItem) = r { - return aChatItem.chatItem + r = await chatApiSendCmd(cmd, bgDelay: msgDelay) + if case let .result(.newChatItems(_, aChatItems)) = r { + return aChatItems.map { $0.chatItem } } - sendMessageErrorAlert(r) + sendMessageErrorAlert(r.unexpected) return nil } } -func apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) async -> ChatItem? { - let r = await chatSendCmd(.apiCreateChatItem(noteFolderId: noteFolderId, file: file, msg: msg)) - if case let .newChatItem(_, aChatItem) = r { return aChatItem.chatItem } - createChatItemErrorAlert(r) +func apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let r: APIResult = await chatApiSendCmd(.apiCreateChatItems(noteFolderId: noteFolderId, composedMessages: composedMessages)) + if case let .result(.newChatItems(_, aChatItems)) = r { return aChatItems.map { $0.chatItem } } + createChatItemsErrorAlert(r.unexpected) return nil } -private func sendMessageErrorAlert(_ r: ChatResponse) { +func apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String) async -> [ChatItem]? { + let r: APIResult = await chatApiSendCmd(.apiReportMessage(groupId: groupId, chatItemId: chatItemId, reportReason: reportReason, reportText: reportText)) + if case let .result(.newChatItems(_, aChatItems)) = r { return aChatItems.map { $0.chatItem } } + + logger.error("apiReportMessage error: \(String(describing: r))") + AlertManager.shared.showAlertMsg( + title: "Error creating report", + message: "Error: \(responseError(r.unexpected))" + ) + return nil +} + +private func sendMessageErrorAlert(_ r: ChatError) { logger.error("send message error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error sending message", @@ -417,43 +482,65 @@ private func sendMessageErrorAlert(_ r: ChatResponse) { ) } -private func createChatItemErrorAlert(_ r: ChatResponse) { - logger.error("apiCreateChatItem error: \(String(describing: r))") +private func createChatItemsErrorAlert(_ r: ChatError) { + logger.error("apiCreateChatItems error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error creating message", message: "Error: \(responseError(r))" ) } -func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool = false) async throws -> ChatItem { - let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, msg: msg, live: live), bgDelay: msgDelay) - if case let .chatItemUpdated(_, aChatItem) = r { return aChatItem.chatItem } - throw r +func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem { + let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay) + switch r { + case let .chatItemUpdated(_, aChatItem): return aChatItem.chatItem + case let .chatItemNotChanged(_, aChatItem): return aChatItem.chatItem + default: throw r.unexpected + } } func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem { - let r = await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) if case let .chatItemReaction(_, _, reaction) = r { return reaction.chatReaction.chatItem } - throw r + throw r.unexpected +} + +func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction) async throws -> [MemberReaction] { + let userId = try currentUserId("apiGetReactionMemebers") + let r: ChatResponse1 = try await chatSendCmd(.apiGetReactionMembers(userId: userId, groupId: groupId, itemId: itemId, reaction: reaction )) + if case let .reactionMembers(_, memberReactions) = r { return memberReactions } + throw r.unexpected } func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { - let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay) if case let .chatItemsDeleted(_, items, _) = r { return items } - throw r + throw r.unexpected } func apiDeleteMemberChatItems(groupId: Int64, itemIds: [Int64]) async throws -> [ChatItemDeletion] { - let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, itemIds: itemIds), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, itemIds: itemIds), bgDelay: msgDelay) if case let .chatItemsDeleted(_, items, _) = r { return items } - throw r + throw r.unexpected +} + +func apiArchiveReceivedReports(groupId: Int64) async throws -> ChatResponse1 { + let r: ChatResponse1 = try await chatSendCmd(.apiArchiveReceivedReports(groupId: groupId), bgDelay: msgDelay) + if case .groupChatItemsDeleted = r { return r } + throw r.unexpected +} + +func apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteReceivedReports(groupId: groupId, itemIds: itemIds, mode: mode), bgDelay: msgDelay) + if case let .chatItemsDeleted(_, chatItemDeletions, _) = r { return chatItemDeletions } + throw r.unexpected } func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String?) { - let r = chatSendCmdSync(.apiGetNtfToken) + let r: APIResult = chatApiSendCmdSync(.apiGetNtfToken) switch r { - case let .ntfToken(token, status, ntfMode, ntfServer): return (token, status, ntfMode, ntfServer) - case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off, nil) + case let .result(.ntfToken(token, status, ntfMode, ntfServer)): return (token, status, ntfMode, ntfServer) + case .error(.errorAgent(.CMD(.PROHIBITED, _))): return (nil, nil, .off, nil) default: logger.debug("apiGetNtfToken response: \(String(describing: r))") return (nil, nil, .off, nil) @@ -461,9 +548,9 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String } func apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) async throws -> NtfTknStatus { - let r = await chatSendCmd(.apiRegisterToken(token: token, notificationMode: notificationMode)) + let r: ChatResponse2 = try await chatSendCmd(.apiRegisterToken(token: token, notificationMode: notificationMode)) if case let .ntfTokenStatus(status) = r { return status } - throw r + throw r.unexpected } func registerToken(token: DeviceToken) { @@ -475,7 +562,12 @@ func registerToken(token: DeviceToken) { Task { do { let status = try await apiRegisterToken(token: token, notificationMode: mode) - await MainActor.run { m.tokenStatus = status } + await MainActor.run { + m.tokenStatus = status + if !status.workingToken { + m.reRegisterTknStatus = status + } + } } catch let error { logger.error("registerToken apiRegisterToken error: \(responseError(error))") } @@ -483,36 +575,129 @@ func registerToken(token: DeviceToken) { } } +func tokenStatusInfo(_ status: NtfTknStatus, register: Bool) -> String { + String.localizedStringWithFormat(NSLocalizedString("Token status: %@.", comment: "token status"), status.text) + + "\n" + status.info(register: register) +} + +func reRegisterToken(token: DeviceToken) { + let m = ChatModel.shared + let mode = m.notificationMode + logger.debug("reRegisterToken \(mode.rawValue)") + Task { + do { + let status = try await apiRegisterToken(token: token, notificationMode: mode) + await MainActor.run { + m.tokenStatus = status + showAlert( + status.workingToken + ? NSLocalizedString("Notifications status", comment: "alert title") + : NSLocalizedString("Notifications error", comment: "alert title"), + message: tokenStatusInfo(status, register: false) + ) + } + } catch let error { + logger.error("reRegisterToken apiRegisterToken error: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error registering for notifications", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + func apiVerifyToken(token: DeviceToken, nonce: String, code: String) async throws { try await sendCommandOkResp(.apiVerifyToken(token: token, nonce: nonce, code: code)) } +func apiCheckToken(token: DeviceToken) async throws -> NtfTknStatus { + let r: ChatResponse2 = try await chatSendCmd(.apiCheckToken(token: token)) + if case let .ntfTokenStatus(status) = r { return status } + throw r.unexpected +} + func apiDeleteToken(token: DeviceToken) async throws { try await sendCommandOkResp(.apiDeleteToken(token: token)) } -func getUserProtoServers(_ serverProtocol: ServerProtocol) throws -> UserProtoServers { - let userId = try currentUserId("getUserProtoServers") - let r = chatSendCmdSync(.apiGetUserProtoServers(userId: userId, serverProtocol: serverProtocol)) - if case let .userProtoServers(_, servers) = r { return servers } - throw r -} - -func setUserProtoServers(_ serverProtocol: ServerProtocol, servers: [ServerCfg]) async throws { - let userId = try currentUserId("setUserProtoServers") - try await sendCommandOkResp(.apiSetUserProtoServers(userId: userId, serverProtocol: serverProtocol, servers: servers)) -} - func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFailure> { let userId = try currentUserId("testProtoServer") - let r = await chatSendCmd(.apiTestProtoServer(userId: userId, server: server)) + let r: ChatResponse0 = try await chatSendCmd(.apiTestProtoServer(userId: userId, server: server)) if case let .serverTestResult(_, _, testFailure) = r { if let t = testFailure { return .failure(t) } return .success(()) } - throw r + throw r.unexpected +} + +func getServerOperators() async throws -> ServerOperatorConditions { + let r: ChatResponse0 = try await chatSendCmd(.apiGetServerOperators) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("getServerOperators error: \(String(describing: r))") + throw r.unexpected +} + +func getServerOperatorsSync() throws -> ServerOperatorConditions { + let r: ChatResponse0 = try chatSendCmdSync(.apiGetServerOperators) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("getServerOperators error: \(String(describing: r))") + throw r.unexpected +} + +func setServerOperators(operators: [ServerOperator]) async throws -> ServerOperatorConditions { + let r: ChatResponse0 = try await chatSendCmd(.apiSetServerOperators(operators: operators)) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("setServerOperators error: \(String(describing: r))") + throw r.unexpected +} + +func getUserServers() async throws -> [UserOperatorServers] { + let userId = try currentUserId("getUserServers") + let r: ChatResponse0 = try await chatSendCmd(.apiGetUserServers(userId: userId)) + if case let .userServers(_, userServers) = r { return userServers } + logger.error("getUserServers error: \(String(describing: r))") + throw r.unexpected +} + +func setUserServers(userServers: [UserOperatorServers]) async throws { + let userId = try currentUserId("setUserServers") + let r: ChatResponse2 = try await chatSendCmd(.apiSetUserServers(userId: userId, userServers: userServers)) + if case .cmdOk = r { return } + logger.error("setUserServers error: \(String(describing: r))") + throw r.unexpected +} + +func validateServers(userServers: [UserOperatorServers]) async throws -> [UserServersError] { + let userId = try currentUserId("validateServers") + let r: ChatResponse0 = try await chatSendCmd(.apiValidateServers(userId: userId, userServers: userServers)) + if case let .userServersValidation(_, serverErrors) = r { return serverErrors } + logger.error("validateServers error: \(String(describing: r))") + throw r.unexpected +} + +func getUsageConditions() async throws -> (UsageConditions, String?, UsageConditions?) { + let r: ChatResponse0 = try await chatSendCmd(.apiGetUsageConditions) + if case let .usageConditions(usageConditions, conditionsText, acceptedConditions) = r { return (usageConditions, conditionsText, acceptedConditions) } + logger.error("getUsageConditions error: \(String(describing: r))") + throw r.unexpected +} + +func setConditionsNotified(conditionsId: Int64) async throws { + let r: ChatResponse2 = try await chatSendCmd(.apiSetConditionsNotified(conditionsId: conditionsId)) + if case .cmdOk = r { return } + logger.error("setConditionsNotified error: \(String(describing: r))") + throw r.unexpected +} + +func acceptConditions(conditionsId: Int64, operatorIds: [Int64]) async throws -> ServerOperatorConditions { + let r: ChatResponse0 = try await chatSendCmd(.apiAcceptConditions(conditionsId: conditionsId, operatorIds: operatorIds)) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("acceptConditions error: \(String(describing: r))") + throw r.unexpected } func getChatItemTTL() throws -> ChatItemTTL { @@ -525,9 +710,15 @@ func getChatItemTTLAsync() async throws -> ChatItemTTL { return try chatItemTTLResponse(await chatSendCmd(.apiGetChatItemTTL(userId: userId))) } -private func chatItemTTLResponse(_ r: ChatResponse) throws -> ChatItemTTL { - if case let .chatItemTTL(_, chatItemTTL) = r { return ChatItemTTL(chatItemTTL) } - throw r +private func chatItemTTLResponse(_ r: ChatResponse0) throws -> ChatItemTTL { + if case let .chatItemTTL(_, chatItemTTL) = r { + if let ttl = chatItemTTL { + return ChatItemTTL(ttl) + } else { + throw RuntimeError("chatItemTTLResponse: invalid ttl") + } + } + throw r.unexpected } func setChatItemTTL(_ chatItemTTL: ChatItemTTL) async throws { @@ -535,22 +726,27 @@ func setChatItemTTL(_ chatItemTTL: ChatItemTTL) async throws { try await sendCommandOkResp(.apiSetChatItemTTL(userId: userId, seconds: chatItemTTL.seconds)) } +func setChatTTL(chatType: ChatType, id: Int64, _ chatItemTTL: ChatTTL) async throws { + let userId = try currentUserId("setChatItemTTL") + try await sendCommandOkResp(.apiSetChatTTL(userId: userId, type: chatType, id: id, seconds: chatItemTTL.value)) +} + func getNetworkConfig() async throws -> NetCfg? { - let r = await chatSendCmd(.apiGetNetworkConfig) + let r: ChatResponse0 = try await chatSendCmd(.apiGetNetworkConfig) if case let .networkConfig(cfg) = r { return cfg } - throw r + throw r.unexpected } func setNetworkConfig(_ cfg: NetCfg, ctrl: chat_ctrl? = nil) throws { - let r = chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg), ctrl) + let r: ChatResponse2 = try chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg), ctrl: ctrl) if case .cmdOk = r { return } - throw r + throw r.unexpected } func apiSetNetworkInfo(_ networkInfo: UserNetworkInfo) throws { - let r = chatSendCmdSync(.apiSetNetworkInfo(networkInfo: networkInfo)) + let r: ChatResponse2 = try chatSendCmdSync(.apiSetNetworkInfo(networkInfo: networkInfo)) if case .cmdOk = r { return } - throw r + throw r.unexpected } func reconnectAllServers() async throws { @@ -571,118 +767,135 @@ func apiSetMemberSettings(_ groupId: Int64, _ groupMemberId: Int64, _ memberSett } func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profile?) { - let r = await chatSendCmd(.apiContactInfo(contactId: contactId)) + let r: ChatResponse0 = try await chatSendCmd(.apiContactInfo(contactId: contactId)) if case let .contactInfo(_, _, connStats, customUserProfile) = r { return (connStats, customUserProfile) } - throw r + throw r.unexpected } -func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, ConnectionStats?) { - let r = chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) +func apiGroupMemberInfoSync(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, ConnectionStats?) { + let r: ChatResponse0 = try chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) } - throw r + throw r.unexpected } -func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { - let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) - if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } - throw r +func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupMember, ConnectionStats?) { + let r: ChatResponse0 = try await chatSendCmd(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) + if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) } + throw r.unexpected } -func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { - let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) +func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { + let r: ChatResponse0 = try await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } - throw r + throw r.unexpected +} + +func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { + let r: ChatResponse0 = try await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) + if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } + throw r.unexpected } func apiSwitchContact(contactId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiSwitchContact(contactId: contactId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSwitchContact(contactId: contactId)) if case let .contactSwitchStarted(_, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiSwitchGroupMember(_ groupId: Int64, _ groupMemberId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberSwitchStarted(_, _, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiAbortSwitchContact(_ contactId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiAbortSwitchContact(contactId: contactId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiAbortSwitchContact(contactId: contactId)) if case let .contactSwitchAborted(_, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiAbortSwitchGroupMember(_ groupId: Int64, _ groupMemberId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiAbortSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiAbortSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberSwitchAborted(_, _, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiSyncContactRatchet(_ contactId: Int64, _ force: Bool) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiSyncContactRatchet(contactId: contactId, force: force)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSyncContactRatchet(contactId: contactId, force: force)) if case let .contactRatchetSyncStarted(_, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiSyncGroupMemberRatchet(_ groupId: Int64, _ groupMemberId: Int64, _ force: Bool) throws -> (GroupMember, ConnectionStats) { - let r = chatSendCmdSync(.apiSyncGroupMemberRatchet(groupId: groupId, groupMemberId: groupMemberId, force: force)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSyncGroupMemberRatchet(groupId: groupId, groupMemberId: groupMemberId, force: force)) if case let .groupMemberRatchetSyncStarted(_, _, member, connectionStats) = r { return (member, connectionStats) } - throw r + throw r.unexpected } func apiGetContactCode(_ contactId: Int64) async throws -> (Contact, String) { - let r = await chatSendCmd(.apiGetContactCode(contactId: contactId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetContactCode(contactId: contactId)) if case let .contactCode(_, contact, connectionCode) = r { return (contact, connectionCode) } - throw r + throw r.unexpected } -func apiGetGroupMemberCode(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, String) { - let r = chatSendCmdSync(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId)) +func apiGetGroupMemberCode(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupMember, String) { + let r: ChatResponse0 = try await chatSendCmd(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberCode(_, _, member, connectionCode) = r { return (member, connectionCode) } - throw r + throw r.unexpected } func apiVerifyContact(_ contactId: Int64, connectionCode: String?) -> (Bool, String)? { - let r = chatSendCmdSync(.apiVerifyContact(contactId: contactId, connectionCode: connectionCode)) - if case let .connectionVerified(_, verified, expectedCode) = r { return (verified, expectedCode) } + let r: APIResult = chatApiSendCmdSync(.apiVerifyContact(contactId: contactId, connectionCode: connectionCode)) + if case let .result(.connectionVerified(_, verified, expectedCode)) = r { return (verified, expectedCode) } logger.error("apiVerifyContact error: \(String(describing: r))") return nil } func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCode: String?) -> (Bool, String)? { - let r = chatSendCmdSync(.apiVerifyGroupMember(groupId: groupId, groupMemberId: groupMemberId, connectionCode: connectionCode)) - if case let .connectionVerified(_, verified, expectedCode) = r { return (verified, expectedCode) } + let r: APIResult = chatApiSendCmdSync(.apiVerifyGroupMember(groupId: groupId, groupMemberId: groupMemberId, connectionCode: connectionCode)) + if case let .result(.connectionVerified(_, verified, expectedCode)) = r { return (verified, expectedCode) } logger.error("apiVerifyGroupMember error: \(String(describing: r))") return nil } -func apiAddContact(incognito: Bool) async -> ((String, PendingContactConnection)?, Alert?) { +func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactConnection)?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiAddContact: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false) - if case let .invitation(_, connReqInvitation, connection) = r { return ((connReqInvitation, connection), nil) } + let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) + let r: APIResult = await chatApiSendCmd(.apiAddContact(userId: userId, short: short, incognito: incognito), bgTask: false) + if case let .result(.invitation(_, connLinkInv, connection)) = r { return ((connLinkInv, connection), nil) } let alert = connectionErrorAlert(r) return (nil, alert) } func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> PendingContactConnection? { - let r = await chatSendCmd(.apiSetConnectionIncognito(connId: connId, incognito: incognito)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetConnectionIncognito(connId: connId, incognito: incognito)) if case let .connectionIncognitoUpdated(_, toConnection) = r { return toConnection } - throw r + throw r.unexpected } -func apiConnectPlan(connReq: String) async throws -> ConnectionPlan { - let userId = try currentUserId("apiConnectPlan") - let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq)) - if case let .connectionPlan(_, connectionPlan) = r { return connectionPlan } - logger.error("apiConnectPlan error: \(responseError(r))") - throw r +func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection { + let r: ChatResponse1 = try await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) + + if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection} + throw r.unexpected } -func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, PendingContactConnection)? { - let (r, alert) = await apiConnect_(incognito: incognito, connReq: connReq) +func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) { + guard let userId = ChatModel.shared.currentUser?.userId else { + logger.error("apiConnectPlan: no current user") + return (nil, nil) + } + let r: APIResult = await chatApiSendCmd(.apiConnectPlan(userId: userId, connLink: connLink)) + if case let .result(.connectionPlan(_, connLink, connPlan)) = r { return ((connLink, connPlan), nil) } + let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) + return (nil, alert) +} + +func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqType, PendingContactConnection)? { + let (r, alert) = await apiConnect_(incognito: incognito, connLink: connLink) if let alert = alert { AlertManager.shared.showAlert(alert) return nil @@ -691,48 +904,74 @@ func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, Pending } } -func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, PendingContactConnection)?, Alert?) { +func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqType, PendingContactConnection)?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiConnect: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connReq: connReq)) + let r: APIResult = await chatApiSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) let m = ChatModel.shared switch r { - case let .sentConfirmation(_, connection): + case let .result(.sentConfirmation(_, connection)): return ((.invitation, connection), nil) - case let .sentInvitation(_, connection): + case let .result(.sentInvitation(_, connection)): return ((.contact, connection), nil) - case let .contactAlreadyExists(_, contact): + case let .result(.contactAlreadyExists(_, contact)): if let c = m.getContactChat(contact.contactId) { ItemsModel.shared.loadOpenChat(c.id) } let alert = contactAlreadyExistsAlert(contact) return (nil, alert) - case .chatCmdError(_, .error(.invalidConnReq)): - let alert = mkAlert( + default: () + } + let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) + return (nil, alert) +} + +private func apiConnectResponseAlert(_ r: ChatError) -> Alert? { + switch r { + case .error(.invalidConnReq): + mkAlert( title: "Invalid connection link", message: "Please check that you used the correct link or ask your contact to send you another one." ) - return (nil, alert) - case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): - let alert = mkAlert( + case .error(.unsupportedConnReq): + mkAlert( + title: "Unsupported connection link", + message: "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." + ) + case .errorAgent(.SMP(_, .AUTH)): + mkAlert( title: "Connection error (AUTH)", message: "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." ) - return (nil, alert) - case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))): + case let .errorAgent(.SMP(_, .BLOCKED(info))): + Alert( + title: Text("Connection blocked"), + message: Text("Connection is blocked by server operator:\n\(info.reason.text)"), + primaryButton: .default(Text("Ok")), + secondaryButton: .default(Text("How it works")) { + DispatchQueue.main.async { + UIApplication.shared.open(contentModerationPostLink) + } + } + ) + case .errorAgent(.SMP(_, .QUOTA)): + mkAlert( + title: "Undelivered messages", + message: "The connection reached the limit of undelivered messages, your contact may be offline." + ) + case let .errorAgent(.INTERNAL(internalErr)): if internalErr == "SEUniqueID" { - let alert = mkAlert( + mkAlert( title: "Already connected?", message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))." ) - return (nil, alert) + } else { + nil } - default: () + default: nil } - let alert = connectionErrorAlert(r) - return (nil, alert) } func contactAlreadyExistsAlert(_ contact: Contact) -> Alert { @@ -742,13 +981,13 @@ func contactAlreadyExistsAlert(_ contact: Contact) -> Alert { ) } -private func connectionErrorAlert(_ r: ChatResponse) -> Alert { +private func connectionErrorAlert(_ r: APIResult) -> Alert { if let networkErrorAlert = networkErrorAlert(r) { return networkErrorAlert } else { return mkAlert( title: "Connection error", - message: "Error: \(responseError(r))" + message: "Error: \(responseError(r.unexpected))" ) } } @@ -758,9 +997,9 @@ func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Co logger.error("apiConnectContactViaAddress: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId)) - if case let .sentInvitationToContact(_, contact, _) = r { return (contact, nil) } - logger.error("apiConnectContactViaAddress error: \(responseError(r))") + let r: APIResult = await chatApiSendCmd(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId)) + if case let .result(.sentInvitationToContact(_, contact, _)) = r { return (contact, nil) } + logger.error("apiConnectContactViaAddress error: \(responseError(r.unexpected))") let alert = connectionErrorAlert(r) return (nil, alert) } @@ -769,11 +1008,11 @@ func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = . let chatId = type.rawValue + id.description DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) } defer { DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } } - let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) if case .direct = type, case .contactDeleted = r { return } if case .contactConnection = type, case .contactConnectionDeleted = r { return } if case .group = type, case .groupDeletedUser = r { return } - throw r + throw r.unexpected } func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws -> Contact { @@ -787,9 +1026,9 @@ func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } } } - let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) if case let .contactDeleted(_, contact) = r { return contact } - throw r + throw r.unexpected } func deleteChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async { @@ -840,9 +1079,9 @@ func deleteContactChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(noti func apiClearChat(type: ChatType, id: Int64) async throws -> ChatInfo { - let r = await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false) if case let .chatCleared(_, updatedChatInfo) = r { return updatedChatInfo } - throw r + throw r.unexpected } func clearChat(_ chat: Chat) async { @@ -857,113 +1096,125 @@ func clearChat(_ chat: Chat) async { func apiListContacts() throws -> [Contact] { let userId = try currentUserId("apiListContacts") - let r = chatSendCmdSync(.apiListContacts(userId: userId)) + let r: ChatResponse1 = try chatSendCmdSync(.apiListContacts(userId: userId)) if case let .contactsList(_, contacts) = r { return contacts } - throw r + throw r.unexpected } func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? { let userId = try currentUserId("apiUpdateProfile") - let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile)) + let r: APIResult = await chatApiSendCmd(.apiUpdateProfile(userId: userId, profile: profile)) switch r { - case .userProfileNoChange: return (profile, []) - case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts) - case .chatCmdError(_, .errorStore(.duplicateName)): return nil; - default: throw r + case .result(.userProfileNoChange): return (profile, []) + case let .result(.userProfileUpdated(_, _, toProfile, updateSummary)): return (toProfile, updateSummary.changedContacts) + case .error(.errorStore(.duplicateName)): return nil; + default: throw r.unexpected } } func apiSetProfileAddress(on: Bool) async throws -> User? { let userId = try currentUserId("apiSetProfileAddress") - let r = await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) switch r { case .userProfileNoChange: return nil case let .userProfileUpdated(user, _, _, _): return user - default: throw r + default: throw r.unexpected } } func apiSetContactPrefs(contactId: Int64, preferences: Preferences) async throws -> Contact? { - let r = await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences)) if case let .contactPrefsUpdated(_, _, toContact) = r { return toContact } - throw r + throw r.unexpected } func apiSetContactAlias(contactId: Int64, localAlias: String) async throws -> Contact? { - let r = await chatSendCmd(.apiSetContactAlias(contactId: contactId, localAlias: localAlias)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetContactAlias(contactId: contactId, localAlias: localAlias)) if case let .contactAliasUpdated(_, toContact) = r { return toContact } - throw r + throw r.unexpected +} + +func apiSetGroupAlias(groupId: Int64, localAlias: String) async throws -> GroupInfo? { + let r: ChatResponse1 = try await chatSendCmd(.apiSetGroupAlias(groupId: groupId, localAlias: localAlias)) + if case let .groupAliasUpdated(_, toGroup) = r { return toGroup } + throw r.unexpected } func apiSetConnectionAlias(connId: Int64, localAlias: String) async throws -> PendingContactConnection? { - let r = await chatSendCmd(.apiSetConnectionAlias(connId: connId, localAlias: localAlias)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetConnectionAlias(connId: connId, localAlias: localAlias)) if case let .connectionAliasUpdated(_, toConnection) = r { return toConnection } - throw r + throw r.unexpected } func apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) async -> Bool { - let r = await chatSendCmd(.apiSetUserUIThemes(userId: userId, themes: themes)) - if case .cmdOk = r { return true } - logger.error("apiSetUserUIThemes bad response: \(String(describing: r))") - return false + do { + try await sendCommandOkResp(.apiSetUserUIThemes(userId: userId, themes: themes)) + return true + } catch { + logger.error("apiSetUserUIThemes bad response: \(responseError(error))") + return false + } } func apiSetChatUIThemes(chatId: ChatId, themes: ThemeModeOverrides?) async -> Bool { - let r = await chatSendCmd(.apiSetChatUIThemes(chatId: chatId, themes: themes)) - if case .cmdOk = r { return true } - logger.error("apiSetChatUIThemes bad response: \(String(describing: r))") - return false + do { + try await sendCommandOkResp(.apiSetChatUIThemes(chatId: chatId, themes: themes)) + return true + } catch { + logger.error("apiSetChatUIThemes bad response: \(responseError(error))") + return false + } } -func apiCreateUserAddress() async throws -> String { +func apiCreateUserAddress(short: Bool) async throws -> CreatedConnLink { let userId = try currentUserId("apiCreateUserAddress") - let r = await chatSendCmd(.apiCreateMyAddress(userId: userId)) - if case let .userContactLinkCreated(_, connReq) = r { return connReq } - throw r + let r: ChatResponse1 = try await chatSendCmd(.apiCreateMyAddress(userId: userId, short: short)) + if case let .userContactLinkCreated(_, connLink) = r { return connLink } + throw r.unexpected } func apiDeleteUserAddress() async throws -> User? { let userId = try currentUserId("apiDeleteUserAddress") - let r = await chatSendCmd(.apiDeleteMyAddress(userId: userId)) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteMyAddress(userId: userId)) if case let .userContactLinkDeleted(user) = r { return user } - throw r + throw r.unexpected } func apiGetUserAddress() throws -> UserContactLink? { let userId = try currentUserId("apiGetUserAddress") - return try userAddressResponse(chatSendCmdSync(.apiShowMyAddress(userId: userId))) + return try userAddressResponse(chatApiSendCmdSync(.apiShowMyAddress(userId: userId))) } func apiGetUserAddressAsync() async throws -> UserContactLink? { let userId = try currentUserId("apiGetUserAddressAsync") - return try userAddressResponse(await chatSendCmd(.apiShowMyAddress(userId: userId))) + return try userAddressResponse(await chatApiSendCmd(.apiShowMyAddress(userId: userId))) } -private func userAddressResponse(_ r: ChatResponse) throws -> UserContactLink? { +private func userAddressResponse(_ r: APIResult) throws -> UserContactLink? { switch r { - case let .userContactLink(_, contactLink): return contactLink - case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil - default: throw r + case let .result(.userContactLink(_, contactLink)): return contactLink + case .error(.errorStore(storeError: .userContactLinkNotFound)): return nil + default: throw r.unexpected } } func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? { let userId = try currentUserId("userAddressAutoAccept") - let r = await chatSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) + let r: APIResult = await chatApiSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) switch r { - case let .userContactLinkUpdated(_, contactLink): return contactLink - case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil - default: throw r + case let .result(.userContactLinkUpdated(_, contactLink)): return contactLink + case .error(.errorStore(storeError: .userContactLinkNotFound)): return nil + default: throw r.unexpected } } func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact? { - let r = await chatSendCmd(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId)) + let r: APIResult = await chatApiSendCmd(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId)) let am = AlertManager.shared - if case let .acceptingContactRequest(_, contact) = r { return contact } - if case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))) = r { + if case let .result(.acceptingContactRequest(_, contact)) = r { return contact } + if case .error(.errorAgent(.SMP(_, .AUTH))) = r { am.showAlertMsg( title: "Connection error (AUTH)", message: "Sender may have deleted the connection request." @@ -974,20 +1225,24 @@ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Cont logger.error("apiAcceptContactRequest error: \(String(describing: r))") am.showAlertMsg( title: "Error accepting contact request", - message: "Error: \(responseError(r))" + message: "Error: \(responseError(r.unexpected))" ) } return nil } func apiRejectContactRequest(contactReqId: Int64) async throws { - let r = await chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) + let r: ChatResponse1 = try await chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) if case .contactRequestRejected = r { return } - throw r + throw r.unexpected } -func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws { - try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange)) +func apiChatRead(type: ChatType, id: Int64) async throws { + try await sendCommandOkResp(.apiChatRead(type: type, id: id)) +} + +func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws { + try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, itemIds: itemIds)) } func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { @@ -995,109 +1250,155 @@ func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { } func uploadStandaloneFile(user: any UserLike, file: CryptoFile, ctrl: chat_ctrl? = nil) async -> (FileTransferMeta?, String?) { - let r = await chatSendCmd(.apiUploadStandaloneFile(userId: user.userId, file: file), ctrl) - if case let .sndStandaloneFileCreated(_, fileTransferMeta) = r { + let r: APIResult = await chatApiSendCmd(.apiUploadStandaloneFile(userId: user.userId, file: file), ctrl: ctrl) + if case let .result(.sndStandaloneFileCreated(_, fileTransferMeta)) = r { return (fileTransferMeta, nil) } else { - logger.error("uploadStandaloneFile error: \(String(describing: r))") - return (nil, responseError(r)) + let err = responseError(r.unexpected) + logger.error("uploadStandaloneFile error: \(err)") + return (nil, err) } } func downloadStandaloneFile(user: any UserLike, url: String, file: CryptoFile, ctrl: chat_ctrl? = nil) async -> (RcvFileTransfer?, String?) { - let r = await chatSendCmd(.apiDownloadStandaloneFile(userId: user.userId, url: url, file: file), ctrl) - if case let .rcvStandaloneFileCreated(_, rcvFileTransfer) = r { + let r: APIResult = await chatApiSendCmd(.apiDownloadStandaloneFile(userId: user.userId, url: url, file: file), ctrl: ctrl) + if case let .result(.rcvStandaloneFileCreated(_, rcvFileTransfer)) = r { return (rcvFileTransfer, nil) } else { - logger.error("downloadStandaloneFile error: \(String(describing: r))") - return (nil, responseError(r)) + let err = responseError(r.unexpected) + logger.error("downloadStandaloneFile error: \(err)") + return (nil, err) } } func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationFileLinkData? { - let r = await chatSendCmd(.apiStandaloneFileInfo(url: url), ctrl) - if case let .standaloneFileInfo(fileMeta) = r { + let r: APIResult = await chatApiSendCmd(.apiStandaloneFileInfo(url: url), ctrl: ctrl) + if case let .result(.standaloneFileInfo(fileMeta)) = r { return fileMeta } else { - logger.error("standaloneFileInfo error: \(String(describing: r))") + logger.error("standaloneFileInfo error: \(responseError(r.unexpected))") return nil } } func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async { - if let chatItem = await apiReceiveFile( - fileId: fileId, - userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), - encrypted: privacyEncryptLocalFilesGroupDefault.get(), + await receiveFiles( + user: user, + fileIds: [fileId], + userApprovedRelays: userApprovedRelays, auto: auto - ) { - await chatItemSimpleUpdate(user, chatItem) - } + ) } -func apiReceiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { - let r = await chatSendCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) - let am = AlertManager.shared - if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } - if case .rcvFileAcceptedSndCancelled = r { - logger.debug("apiReceiveFile error: sender cancelled file transfer") - if !auto { - am.showAlertMsg( - title: "Cannot receive file", - message: "Sender cancelled file transfer." +func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool = false, auto: Bool = false) async { + var fileIdsToApprove: [Int64] = [] + var srvsToApprove: Set = [] + var otherFileErrs: [APIResult] = [] + + for fileId in fileIds { + let r: APIResult = await chatApiSendCmd( + .receiveFile( + fileId: fileId, + userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), + encrypted: privacyEncryptLocalFilesGroupDefault.get(), + inline: nil ) + ) + switch r { + case let .result(.rcvFileAccepted(_, chatItem)): + await chatItemSimpleUpdate(user, chatItem) + // TODO when aChatItem added + // case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): + // await chatItemSimpleUpdate(user, aChatItem) + // Task { cleanupFile(aChatItem) } + case let .error(.error(.fileNotApproved(fileId, unknownServers))): + fileIdsToApprove.append(fileId) + srvsToApprove.formUnion(unknownServers) + default: + otherFileErrs.append(r) } - } else if let networkErrorAlert = networkErrorAlert(r) { - logger.error("apiReceiveFile network error: \(String(describing: r))") - if !auto { - am.showAlert(networkErrorAlert) - } - } else { - switch chatError(r) { - case .fileCancelled: - logger.debug("apiReceiveFile ignoring fileCancelled error") - case .fileAlreadyReceiving: - logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error") - case let .fileNotApproved(fileId, unknownServers): - logger.debug("apiReceiveFile fileNotApproved error") - if !auto { - let srvs = unknownServers.map { s in + } + + if !auto { + let otherErrsStr = fileErrorStrs(otherFileErrs) + // If there are not approved files, alert is shown the same way both in case of singular and plural files reception + if !fileIdsToApprove.isEmpty { + let srvs = srvsToApprove + .map { s in if let srv = parseServerAddress(s), !srv.hostnames.isEmpty { srv.hostnames[0] } else { serverHost(s) } } - am.showAlert(Alert( - title: Text("Unknown servers!"), - message: Text("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs.sorted().joined(separator: ", "))."), - primaryButton: .default( - Text("Download"), - action: { - Task { - logger.debug("apiReceiveFile fileNotApproved alert - in Task") - if let user = ChatModel.shared.currentUser { - await receiveFile(user: user, fileId: fileId, userApprovedRelays: true) - } + .sorted() + .joined(separator: ", ") + let fIds = fileIdsToApprove + await MainActor.run { + showAlert( + title: NSLocalizedString("Unknown servers!", comment: "alert title"), + message: ( + String.localizedStringWithFormat(NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.", comment: "alert message"), srvs) + + (otherErrsStr != "" ? "\n\n" + String.localizedStringWithFormat(NSLocalizedString("Other file errors:\n%@", comment: "alert message"), otherErrsStr) : "") + ), + buttonTitle: NSLocalizedString("Download", comment: "alert button"), + buttonAction: { + Task { + logger.debug("apiReceiveFile fileNotApproved alert - in Task") + if let user = ChatModel.shared.currentUser { + await receiveFiles(user: user, fileIds: fIds, userApprovedRelays: true) } } - ), - secondaryButton: .cancel() - )) + }, + cancelButton: true + ) } - default: - logger.error("apiReceiveFile error: \(String(describing: r))") - if !auto { - am.showAlertMsg( - title: "Error receiving file", - message: "Error: \(responseError(r))" + } else if otherFileErrs.count == 1 { // If there is a single other error, we differentiate on it + let errorResponse = otherFileErrs.first! + switch errorResponse { + case let .result(.rcvFileAcceptedSndCancelled(_, rcvFileTransfer)): + logger.debug("receiveFiles error: sender cancelled file transfer \(rcvFileTransfer.fileId)") + await MainActor.run { + showAlert( + NSLocalizedString("Cannot receive file", comment: "alert title"), + message: NSLocalizedString("Sender cancelled file transfer.", comment: "alert message") + ) + } + case .error(.error(.fileCancelled)), .error(.error(.fileAlreadyReceiving)): + logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") + default: + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: responseError(errorResponse.unexpected) + ) + } + } + } else if otherFileErrs.count > 1 { // If there are multiple other errors, we show general alert + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("File errors:\n%@", comment: "alert message"), otherErrsStr) ) } } } - return nil + + func fileErrorStrs(_ errs: [APIResult]) -> String { + var errStr = "" + if errs.count >= 1 { + errStr = String(describing: errs[0].unexpected) + } + if errs.count >= 2 { + errStr += "\n\(String(describing: errs[1].unexpected))" + } + if errs.count > 2 { + errStr += "\nand \(errs.count - 2) other error(s)" + } + return errStr + } } - + func cancelFile(user: User, fileId: Int64) async { if let chatItem = await apiCancelFile(fileId: fileId) { await chatItemSimpleUpdate(user, chatItem) @@ -1106,12 +1407,12 @@ func cancelFile(user: User, fileId: Int64) async { } func apiCancelFile(fileId: Int64, ctrl: chat_ctrl? = nil) async -> AChatItem? { - let r = await chatSendCmd(.cancelFile(fileId: fileId), ctrl) + let r: APIResult = await chatApiSendCmd(.cancelFile(fileId: fileId), ctrl: ctrl) switch r { - case let .sndFileCancelled(_, chatItem, _, _) : return chatItem - case let .rcvFileCancelled(_, chatItem, _) : return chatItem + case let .result(.sndFileCancelled(_, chatItem, _, _)) : return chatItem + case let .result(.rcvFileCancelled(_, chatItem, _)) : return chatItem default: - logger.error("apiCancelFile error: \(String(describing: r))") + logger.error("apiCancelFile error: \(responseError(r.unexpected))") return nil } } @@ -1121,9 +1422,9 @@ func setLocalDeviceName(_ displayName: String) throws { } func connectRemoteCtrl(desktopAddress: String) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) { - let r = await chatSendCmd(.connectRemoteCtrl(xrcpInvitation: desktopAddress)) + let r: ChatResponse2 = try await chatSendCmd(.connectRemoteCtrl(xrcpInvitation: desktopAddress)) if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) } - throw r + throw r.unexpected } func findKnownRemoteCtrl() async throws { @@ -1131,21 +1432,21 @@ func findKnownRemoteCtrl() async throws { } func confirmRemoteCtrl(_ rcId: Int64) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) { - let r = await chatSendCmd(.confirmRemoteCtrl(remoteCtrlId: rcId)) + let r: ChatResponse2 = try await chatSendCmd(.confirmRemoteCtrl(remoteCtrlId: rcId)) if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) } - throw r + throw r.unexpected } func verifyRemoteCtrlSession(_ sessCode: String) async throws -> RemoteCtrlInfo { - let r = await chatSendCmd(.verifyRemoteCtrlSession(sessionCode: sessCode)) + let r: ChatResponse2 = try await chatSendCmd(.verifyRemoteCtrlSession(sessionCode: sessCode)) if case let .remoteCtrlConnected(rc) = r { return rc } - throw r + throw r.unexpected } func listRemoteCtrls() throws -> [RemoteCtrlInfo] { - let r = chatSendCmdSync(.listRemoteCtrls) + let r: ChatResponse2 = try chatSendCmdSync(.listRemoteCtrls) if case let .remoteCtrlList(rcInfo) = r { return rcInfo } - throw r + throw r.unexpected } func stopRemoteCtrl() async throws { @@ -1156,8 +1457,8 @@ func deleteRemoteCtrl(_ rcId: Int64) async throws { try await sendCommandOkResp(.deleteRemoteCtrl(remoteCtrlId: rcId)) } -func networkErrorAlert(_ r: ChatResponse) -> Alert? { - if let alert = getNetworkErrorAlert(r) { +func networkErrorAlert(_ res: APIResult) -> Alert? { + if case let .error(e) = res, let alert = getNetworkErrorAlert(e) { return mkAlert(title: alert.title, message: alert.message) } else { return nil @@ -1219,15 +1520,15 @@ func apiEndCall(_ contact: Contact) async throws { } func apiGetCallInvitationsSync() throws -> [RcvCallInvitation] { - let r = chatSendCmdSync(.apiGetCallInvitations) + let r: ChatResponse2 = try chatSendCmdSync(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } - throw r + throw r.unexpected } func apiGetCallInvitations() async throws -> [RcvCallInvitation] { - let r = await chatSendCmd(.apiGetCallInvitations) + let r: ChatResponse2 = try await chatSendCmd(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } - throw r + throw r.unexpected } func apiCallStatus(_ contact: Contact, _ status: String) async throws { @@ -1239,20 +1540,18 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws { } func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] { - let r = chatSendCmdSync(.apiGetNetworkStatuses) + let r: ChatResponse1 = try chatSendCmdSync(.apiGetNetworkStatuses) if case let .networkStatuses(_, statuses) = r { return statuses } - throw r + throw r.unexpected } -func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async { +func markChatRead(_ chat: Chat) async { do { if chat.chatStats.unreadCount > 0 { - let minItemId = chat.chatStats.minUnreadItemId - let itemRange = (minItemId, aboveItem?.id ?? chat.chatItems.last?.id ?? minItemId) let cInfo = chat.chatInfo - try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange) + try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId) await MainActor.run { - withAnimation { ChatModel.shared.markChatItemsRead(cInfo, aboveItem: aboveItem) } + withAnimation { ChatModel.shared.markAllChatItemsRead(cInfo) } } } if chat.chatStats.unreadChat { @@ -1275,39 +1574,40 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async { } } -func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { +func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async { do { - logger.debug("apiMarkChatItemRead: \(cItem.id)") - try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) - await ChatModel.shared.markChatItemRead(cInfo, cItem) + try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds) + DispatchQueue.main.async { + ChatModel.shared.markChatItemsRead(cInfo, itemIds, mentionsRead) + } } catch { - logger.error("apiMarkChatItemRead apiChatRead error: \(responseError(error))") + logger.error("apiChatItemsRead error: \(responseError(error))") } } -private func sendCommandOkResp(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) async throws { - let r = await chatSendCmd(cmd, ctrl) +private func sendCommandOkResp(_ cmd: ChatCommand, ctrl: chat_ctrl? = nil) async throws { + let r: ChatResponse2 = try await chatSendCmd(cmd, ctrl: ctrl) if case .cmdOk = r { return } - throw r + throw r.unexpected } private func sendCommandOkRespSync(_ cmd: ChatCommand) throws { - let r = chatSendCmdSync(cmd) + let r: ChatResponse2 = try chatSendCmdSync(cmd) if case .cmdOk = r { return } - throw r + throw r.unexpected } func apiNewGroup(incognito: Bool, groupProfile: GroupProfile) throws -> GroupInfo { let userId = try currentUserId("apiNewGroup") - let r = chatSendCmdSync(.apiNewGroup(userId: userId, incognito: incognito, groupProfile: groupProfile)) + let r: ChatResponse2 = try chatSendCmdSync(.apiNewGroup(userId: userId, incognito: incognito, groupProfile: groupProfile)) if case let .groupCreated(_, groupInfo) = r { return groupInfo } - throw r + throw r.unexpected } func apiAddMember(_ groupId: Int64, _ contactId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember { - let r = await chatSendCmd(.apiAddMember(groupId: groupId, contactId: contactId, memberRole: memberRole)) + let r: ChatResponse2 = try await chatSendCmd(.apiAddMember(groupId: groupId, contactId: contactId, memberRole: memberRole)) if case let .sentGroupInvitation(_, _, _, member) = r { return member } - throw r + throw r.unexpected } enum JoinGroupResult { @@ -1317,31 +1617,31 @@ enum JoinGroupResult { } func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult { - let r = await chatSendCmd(.apiJoinGroup(groupId: groupId)) + let r: APIResult = await chatApiSendCmd(.apiJoinGroup(groupId: groupId)) switch r { - case let .userAcceptedGroupSent(_, groupInfo, _): return .joined(groupInfo: groupInfo) - case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): return .invitationRemoved - case .chatCmdError(_, .errorStore(.groupNotFound)): return .groupNotFound - default: throw r + case let .result(.userAcceptedGroupSent(_, groupInfo, _)): return .joined(groupInfo: groupInfo) + case .error(.errorAgent(.SMP(_, .AUTH))): return .invitationRemoved + case .error(.errorStore(.groupNotFound)): return .groupNotFound + default: throw r.unexpected } } -func apiRemoveMember(_ groupId: Int64, _ memberId: Int64) async throws -> GroupMember { - let r = await chatSendCmd(.apiRemoveMember(groupId: groupId, memberId: memberId), bgTask: false) - if case let .userDeletedMember(_, _, member) = r { return member } - throw r +func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> [GroupMember] { + let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false) + if case let .userDeletedMembers(_, _, members, withMessages) = r { return members } + throw r.unexpected } -func apiMemberRole(_ groupId: Int64, _ memberId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember { - let r = await chatSendCmd(.apiMemberRole(groupId: groupId, memberId: memberId, memberRole: memberRole), bgTask: false) - if case let .memberRoleUser(_, _, member, _, _) = r { return member } - throw r +func apiMembersRole(_ groupId: Int64, _ memberIds: [Int64], _ memberRole: GroupMemberRole) async throws -> [GroupMember] { + let r: ChatResponse2 = try await chatSendCmd(.apiMembersRole(groupId: groupId, memberIds: memberIds, memberRole: memberRole), bgTask: false) + if case let .membersRoleUser(_, _, members, _) = r { return members } + throw r.unexpected } -func apiBlockMemberForAll(_ groupId: Int64, _ memberId: Int64, _ blocked: Bool) async throws -> GroupMember { - let r = await chatSendCmd(.apiBlockMemberForAll(groupId: groupId, memberId: memberId, blocked: blocked), bgTask: false) - if case let .memberBlockedForAllUser(_, _, member, _) = r { return member } - throw r +func apiBlockMembersForAll(_ groupId: Int64, _ memberIds: [Int64], _ blocked: Bool) async throws -> [GroupMember] { + let r: ChatResponse2 = try await chatSendCmd(.apiBlockMembersForAll(groupId: groupId, memberIds: memberIds, blocked: blocked), bgTask: false) + if case let .membersBlockedForAllUser(_, _, members, _) = r { return members } + throw r.unexpected } func leaveGroup(_ groupId: Int64) async { @@ -1354,14 +1654,15 @@ func leaveGroup(_ groupId: Int64) async { } func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo { - let r = await chatSendCmd(.apiLeaveGroup(groupId: groupId), bgTask: false) + let r: ChatResponse2 = try await chatSendCmd(.apiLeaveGroup(groupId: groupId), bgTask: false) if case let .leftMemberUser(_, groupInfo) = r { return groupInfo } - throw r + throw r.unexpected } +// use ChatModel's loadGroupMembers from views func apiListMembers(_ groupId: Int64) async -> [GroupMember] { - let r = await chatSendCmd(.apiListMembers(groupId: groupId)) - if case let .groupMembers(_, group) = r { return group.members } + let r: APIResult = await chatApiSendCmd(.apiListMembers(groupId: groupId)) + if case let .result(.groupMembers(_, group)) = r { return group.members } return [] } @@ -1374,72 +1675,73 @@ func filterMembersToAdd(_ ms: [GMember]) -> [Contact] { } func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws -> GroupInfo { - let r = await chatSendCmd(.apiUpdateGroupProfile(groupId: groupId, groupProfile: groupProfile)) + let r: ChatResponse2 = try await chatSendCmd(.apiUpdateGroupProfile(groupId: groupId, groupProfile: groupProfile)) if case let .groupUpdated(_, toGroup) = r { return toGroup } - throw r + throw r.unexpected } -func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) { - let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole)) - if case let .groupLinkCreated(_, _, connReq, memberRole) = r { return (connReq, memberRole) } - throw r +func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { + let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) + let r: ChatResponse2 = try await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short)) + if case let .groupLinkCreated(_, _, connLink, memberRole) = r { return (connLink, memberRole) } + throw r.unexpected } -func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) { - let r = await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole)) - if case let .groupLink(_, _, connReq, memberRole) = r { return (connReq, memberRole) } - throw r +func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { + let r: ChatResponse2 = try await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole)) + if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) } + throw r.unexpected } func apiDeleteGroupLink(_ groupId: Int64) async throws { - let r = await chatSendCmd(.apiDeleteGroupLink(groupId: groupId)) + let r: ChatResponse2 = try await chatSendCmd(.apiDeleteGroupLink(groupId: groupId)) if case .groupLinkDeleted = r { return } - throw r + throw r.unexpected } -func apiGetGroupLink(_ groupId: Int64) throws -> (String, GroupMemberRole)? { - let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId)) +func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRole)? { + let r: APIResult = chatApiSendCmdSync(.apiGetGroupLink(groupId: groupId)) switch r { - case let .groupLink(_, _, connReq, memberRole): - return (connReq, memberRole) - case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)): + case let .result(.groupLink(_, _, connLink, memberRole)): + return (connLink, memberRole) + case .error(.errorStore(storeError: .groupLinkNotFound)): return nil - default: throw r + default: throw r.unexpected } } func apiCreateMemberContact(_ groupId: Int64, _ groupMemberId: Int64) async throws -> Contact { - let r = await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse2 = try await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) if case let .newMemberContact(_, contact, _, _) = r { return contact } - throw r + throw r.unexpected } func apiSendMemberContactInvitation(_ contactId: Int64, _ msg: MsgContent) async throws -> Contact { - let r = await chatSendCmd(.apiSendMemberContactInvitation(contactId: contactId, msg: msg), bgDelay: msgDelay) + let r: ChatResponse2 = try await chatSendCmd(.apiSendMemberContactInvitation(contactId: contactId, msg: msg), bgDelay: msgDelay) if case let .newMemberContactSentInv(_, contact, _, _) = r { return contact } - throw r + throw r.unexpected } func apiGetVersion() throws -> CoreVersionInfo { - let r = chatSendCmdSync(.showVersion) + let r: ChatResponse2 = try chatSendCmdSync(.showVersion) if case let .versionInfo(info, _, _) = r { return info } - throw r + throw r.unexpected } func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) { let userId = try currentUserId("getAgentSubsTotal") - let r = await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false) + let r: ChatResponse2 = try await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false) if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } logger.error("getAgentSubsTotal error: \(String(describing: r))") - throw r + throw r.unexpected } func getAgentServersSummary() throws -> PresentedServersSummary { let userId = try currentUserId("getAgentServersSummary") - let r = chatSendCmdSync(.getAgentServersSummary(userId: userId), log: false) + let r: ChatResponse2 = try chatSendCmdSync(.getAgentServersSummary(userId: userId), log: false) if case let .agentServersSummary(_, serversSummary) = r { return serversSummary } logger.error("getAgentServersSummary error: \(String(describing: r))") - throw r + throw r.unexpected } func resetAgentServersStats() async throws { @@ -1469,6 +1771,16 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get()) m.chatInitialized = true m.currentUser = try apiGetActiveUser() + m.conditions = try getServerOperatorsSync() + if shouldImportAppSettingsDefault.get() { + do { + let appSettings = try apiGetAppSettings(settings: AppSettings.current.prepareForExport()) + appSettings.importIntoApp() + shouldImportAppSettingsDefault.set(false) + } catch { + logger.error("Error while importing app settings: \(error)") + } + } if m.currentUser == nil { onboardingStageDefault.set(.step1_SimpleXInfo) privacyDeliveryReceiptsSet.set(true) @@ -1513,7 +1825,7 @@ private func chatInitialized(start: Bool, refreshInvitations: Bool) throws { } } -func startChat(refreshInvitations: Bool = true) throws { +func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws { logger.debug("startChat") let m = ChatModel.shared try setNetworkConfig(getNetCfg()) @@ -1532,13 +1844,15 @@ func startChat(refreshInvitations: Bool = true) throws { if let token = m.deviceToken { registerToken(token: token) } - withAnimation { - let savedOnboardingStage = onboardingStageDefault.get() - m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 - ? .step3_CreateSimpleXAddress - : savedOnboardingStage - if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { - m.setDeliveryReceipts = true + if !onboarding { + withAnimation { + let savedOnboardingStage = onboardingStageDefault.get() + m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 + ? .step3_ChooseServerOperators + : savedOnboardingStage + if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { + m.setDeliveryReceipts = true + } } } } @@ -1597,24 +1911,37 @@ func getUserChatData() throws { m.userAddress = try apiGetUserAddress() m.chatItemTTL = try getChatItemTTL() let chats = try apiGetChats() + let tags = try apiGetChatTags() m.updateChats(chats) + let tm = ChatTagsModel.shared + tm.activeFilter = nil + tm.userTags = tags + tm.updateChatTags(m.chats) } private func getUserChatDataAsync() async throws { let m = ChatModel.shared + let tm = ChatTagsModel.shared if m.currentUser != nil { let userAddress = try await apiGetUserAddressAsync() let chatItemTTL = try await getChatItemTTLAsync() let chats = try await apiGetChatsAsync() + let tags = try await apiGetChatTagsAsync() await MainActor.run { m.userAddress = userAddress m.chatItemTTL = chatItemTTL m.updateChats(chats) + tm.activeFilter = nil + tm.userTags = tags + tm.updateChatTags(m.chats) } } else { await MainActor.run { m.userAddress = nil m.updateChats([]) + tm.activeFilter = nil + tm.userTags = [] + tm.presetTags = [:] } } } @@ -1624,7 +1951,7 @@ class ChatReceiver { private var receiveMessages = true private var _lastMsgTime = Date.now - var messagesChannel: ((ChatResponse) -> Void)? = nil + var messagesChannel: ((APIResult) -> Void)? = nil static let shared = ChatReceiver() @@ -1642,7 +1969,12 @@ class ChatReceiver { while self.receiveMessages { if let msg = await chatRecvMsg() { self._lastMsgTime = .now - await processReceivedMsg(msg) + Task { await TerminalItems.shared.addResult(msg) } + switch msg { + case let .result(evt): await processReceivedMsg(evt) + case let .error(err): logger.debug("chatRecvMsg error: \(responseError(err))") + case let .invalid(type, json): logger.debug("chatRecvMsg event: * \(type) \(dataToString(json))") + } if let messagesChannel { messagesChannel(msg) } @@ -1659,10 +1991,7 @@ class ChatReceiver { } } -func processReceivedMsg(_ res: ChatResponse) async { - Task { - await TerminalItems.shared.add(.resp(.now, res)) - } +func processReceivedMsg(_ res: ChatEvent) async { let m = ChatModel.shared let n = NetworkModel.shared logger.debug("processReceivedMsg: \(res.responseType)") @@ -1775,36 +2104,48 @@ func processReceivedMsg(_ res: ChatResponse) async { n.networkStatuses = ns } } - case let .newChatItem(user, aChatItem): - let cInfo = aChatItem.chatInfo - let cItem = aChatItem.chatItem - await MainActor.run { - if active(user) { - m.addChatItem(cInfo, cItem) - } else if cItem.isRcvNew && cInfo.ntfsEnabled { - m.increaseUnreadCounter(user: user) + case let .newChatItems(user, chatItems): + for chatItem in chatItems { + let cInfo = chatItem.chatInfo + let cItem = chatItem.chatItem + await MainActor.run { + if active(user) { + m.addChatItem(cInfo, cItem) + if cItem.isActiveReport { + m.increaseGroupReportsCounter(cInfo.id) + } + } else if cItem.isRcvNew && cInfo.ntfsEnabled(chatItem: cItem) { + m.increaseUnreadCounter(user: user) + } + } + if let file = cItem.autoReceiveFile() { + Task { + await receiveFile(user: user, fileId: file.fileId, auto: true) + } + } + if cItem.showNotification { + NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } } - if let file = cItem.autoReceiveFile() { - Task { - await receiveFile(user: user, fileId: file.fileId, auto: true) + case let .chatItemsStatusesUpdated(user, chatItems): + for chatItem in chatItems { + let cInfo = chatItem.chatInfo + let cItem = chatItem.chatItem + if !cItem.isDeletedContent && active(user) { + await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } } - } - if cItem.showNotification { - NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) - } - case let .chatItemStatusUpdated(user, aChatItem): - let cInfo = aChatItem.chatInfo - let cItem = aChatItem.chatItem - if !cItem.isDeletedContent && active(user) { - await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } - } - if let endTask = m.messageDelivery[cItem.id] { - switch cItem.meta.itemStatus { - case .sndSent: endTask() - case .sndErrorAuth: endTask() - case .sndError: endTask() - default: () + if let endTask = m.messageDelivery[cItem.id] { + switch cItem.meta.itemStatus { + case .sndNew: () + case .sndSent: endTask() + case .sndRcvd: endTask() + case .sndErrorAuth: endTask() + case .sndError: endTask() + case .sndWarning: endTask() + case .rcvNew: () + case .rcvRead: () + case .invalid: () + } } } case let .chatItemUpdated(user, aChatItem): @@ -1818,7 +2159,8 @@ func processReceivedMsg(_ res: ChatResponse) async { case let .chatItemsDeleted(user, items, _): if !active(user) { for item in items { - if item.toChatItem == nil && item.deletedChatItem.chatItem.isRcvNew && item.deletedChatItem.chatInfo.ntfsEnabled { + let d = item.deletedChatItem + if item.toChatItem == nil && d.chatItem.isRcvNew && d.chatInfo.ntfsEnabled(chatItem: d.chatItem) { await MainActor.run { m.decreaseUnreadCounter(user: user) } @@ -1834,8 +2176,13 @@ func processReceivedMsg(_ res: ChatResponse) async { } else { m.removeChatItem(item.deletedChatItem.chatInfo, item.deletedChatItem.chatItem) } + if item.deletedChatItem.chatItem.isActiveReport { + m.decreaseGroupReportsCounter(item.deletedChatItem.chatInfo.id) + } } } + case let .groupChatItemsDeleted(user, groupInfo, chatItemIDs, _, member_): + await groupChatItemsDeleted(user, groupInfo, chatItemIDs, member_) case let .receivedGroupInvitation(user, groupInfo, _, _): if active(user) { await MainActor.run { @@ -1855,7 +2202,7 @@ func processReceivedMsg(_ res: ChatResponse) async { } case let .groupLinkConnecting(user, groupInfo, hostMember): if !active(user) { return } - + await MainActor.run { m.updateGroup(groupInfo) if let hostConn = hostMember.activeConn { @@ -1863,22 +2210,40 @@ func processReceivedMsg(_ res: ChatResponse) async { m.removeChat(hostConn.id) } } + case let .businessLinkConnecting(user, groupInfo, _, fromContact): + if !active(user) { return } + + await MainActor.run { + m.updateGroup(groupInfo) + } + if m.chatId == fromContact.id { + ItemsModel.shared.loadOpenChat(groupInfo.id) + } + await MainActor.run { + m.removeChat(fromContact.id) + } case let .joinedGroupMemberConnecting(user, groupInfo, _, member): if active(user) { await MainActor.run { _ = m.upsertGroupMember(groupInfo, member) } } - case let .deletedMemberUser(user, groupInfo, _): // TODO update user member + case let .deletedMemberUser(user, groupInfo, member, withMessages): // TODO update user member if active(user) { await MainActor.run { m.updateGroup(groupInfo) + if withMessages { + m.removeMemberItems(groupInfo.membership, byMember: member, groupInfo) + } } } - case let .deletedMember(user, groupInfo, _, deletedMember): + case let .deletedMember(user, groupInfo, byMember, deletedMember, withMessages): if active(user) { await MainActor.run { _ = m.upsertGroupMember(groupInfo, deletedMember) + if withMessages { + m.removeMemberItems(deletedMember, byMember: byMember, groupInfo) + } } } case let .leftMember(user, groupInfo, member): @@ -1944,6 +2309,10 @@ func processReceivedMsg(_ res: ChatResponse) async { } case let .rcvFileAccepted(user, aChatItem): // usually rcvFileAccepted is a response, but it's also an event for XFTP files auto-accepted from NSE await chatItemSimpleUpdate(user, aChatItem) +// TODO when aChatItem added +// case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): // usually rcvFileAcceptedSndCancelled is a response, but it's also an event for XFTP files auto-accepted from NSE +// await chatItemSimpleUpdate(user, aChatItem) +// Task { cleanupFile(aChatItem) } case let .rcvFileStart(user, aChatItem): await chatItemSimpleUpdate(user, aChatItem) case let .rcvFileComplete(user, aChatItem): @@ -1998,7 +2367,6 @@ func processReceivedMsg(_ res: ChatResponse) async { await withCall(contact) { call in await MainActor.run { call.callState = .offerReceived - call.peerMedia = callType.media call.sharedKey = sharedKey } let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY) @@ -2167,6 +2535,43 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async { } } +func groupChatItemsDeleted(_ user: UserRef, _ groupInfo: GroupInfo, _ chatItemIDs: Set, _ member_: GroupMember?) async { + let m = ChatModel.shared + if !active(user) { + do { + let users = try listUsers() + await MainActor.run { + m.users = users + } + } catch { + logger.error("Error loading users: \(error)") + } + return + } + let im = ItemsModel.shared + let cInfo = ChatInfo.group(groupInfo: groupInfo) + await MainActor.run { + m.decreaseGroupReportsCounter(cInfo.id, by: chatItemIDs.count) + } + var notFound = chatItemIDs.count + for ci in im.reversedChatItems { + if chatItemIDs.contains(ci.id) { + let deleted = if case let .groupRcv(groupMember) = ci.chatDir, let member_, groupMember.groupMemberId != member_.groupMemberId { + CIDeleted.moderated(deletedTs: Date.now, byGroupMember: member_) + } else { + CIDeleted.deleted(deletedTs: Date.now) + } + await MainActor.run { + var newItem = ci + newItem.meta.itemDeleted = deleted + _ = m.upsertChatItem(cInfo, newItem) + } + notFound -= 1 + if notFound == 0 { break } + } + } +} + func refreshCallInvitations() async throws { let m = ChatModel.shared let callInvitations = try await apiGetCallInvitations() @@ -2182,9 +2587,11 @@ func refreshCallInvitations() async throws { } } -func justRefreshCallInvitations() throws { +func justRefreshCallInvitations() async throws { let callInvitations = try apiGetCallInvitationsSync() - ChatModel.shared.callInvitations = callsByChat(callInvitations) + await MainActor.run { + ChatModel.shared.callInvitations = callsByChat(callInvitations) + } } private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: RcvCallInvitation] { @@ -2194,12 +2601,13 @@ private func callsByChat(_ callInvitations: [RcvCallInvitation]) -> [ChatId: Rcv } func activateCall(_ callInvitation: RcvCallInvitation) { - if !callInvitation.user.showNotifications { return } let m = ChatModel.shared + logger.debug("reportNewIncomingCall activeCallUUID \(String(describing: m.activeCall?.callUUID)) invitationUUID \(String(describing: callInvitation.callUUID))") + if !callInvitation.user.showNotifications || m.activeCall?.callUUID == callInvitation.callUUID { return } CallController.shared.reportNewIncomingCall(invitation: callInvitation) { error in if let error = error { DispatchQueue.main.async { - m.callInvitations[callInvitation.contact.id]?.callkitUUID = nil + m.callInvitations[callInvitation.contact.id]?.callUUID = nil } logger.error("reportNewIncomingCall error: \(error.localizedDescription)") } else { diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 7f2c3b5866..f8d69c5fc8 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -19,6 +19,7 @@ struct SimpleXApp: App { @Environment(\.scenePhase) var scenePhase @State private var enteredBackgroundAuthenticated: TimeInterval? = nil + @State private var appOpenUrlLater: URL? init() { DispatchQueue.global(qos: .background).sync { @@ -42,7 +43,11 @@ struct SimpleXApp: App { .environmentObject(AppTheme.shared) .onOpenURL { url in logger.debug("ContentView.onOpenURL: \(url)") - chatModel.appOpenUrl = url + if AppChatState.shared.value == .active { + chatModel.appOpenUrl = url + } else { + appOpenUrlLater = url + } } .onAppear() { // Present screen for continue migration if it wasn't finished yet @@ -82,12 +87,27 @@ struct SimpleXApp: App { if appState != .stopped { startChatAndActivate { - if appState.inactive && chatModel.chatRunning == true { - Task { - await updateChats() - if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { - await updateCallInvitations() + if chatModel.chatRunning == true { + if let ntfResponse = chatModel.notificationResponse { + chatModel.notificationResponse = nil + NtfManager.shared.processNotificationResponse(ntfResponse) + } + if appState.inactive { + Task { + await updateChats() + if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { + await updateCallInvitations() + } + if let url = appOpenUrlLater { + await MainActor.run { + appOpenUrlLater = nil + chatModel.appOpenUrl = url + } + } } + } else if let url = appOpenUrlLater { + appOpenUrlLater = nil + chatModel.appOpenUrl = url } } } @@ -137,7 +157,8 @@ struct SimpleXApp: App { let chats = try await apiGetChatsAsync() await MainActor.run { chatModel.updateChats(chats) } if let id = chatModel.chatId, - let chat = chatModel.getChat(id) { + let chat = chatModel.getChat(id), + !NtfManager.shared.navigatingToChat { Task { await loadChat(chat: chat, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift index e2641eb8dd..de67390026 100644 --- a/apps/ios/Shared/Theme/Theme.swift +++ b/apps/ios/Shared/Theme/Theme.swift @@ -102,7 +102,7 @@ extension ThemeWallpaper { public func importFromString() -> ThemeWallpaper { if preset == nil, let image { // Need to save image from string and to save its path - if let parsed = UIImage(base64Encoded: image), + if let parsed = imageFromBase64(image), let filename = saveWallpaperFile(image: parsed) { var copy = self copy.image = nil @@ -122,7 +122,7 @@ extension ThemeWallpaper { let preset: String? = if case let WallpaperType.preset(filename, _) = type { filename } else { nil } let scale: Float? = if case let WallpaperType.preset(_, scale) = type { scale } else { if case let WallpaperType.image(_, scale, _) = type { scale } else { 1.0 } } let scaleType: WallpaperScaleType? = if case let WallpaperType.image(_, _, scaleType) = type { scaleType } else { nil } - let image: String? = if case WallpaperType.image = type, let image = type.uiImage { resizeImageToStrSize(image, maxDataSize: 5_000_000) } else { nil } + let image: String? = if case WallpaperType.image = type, let image = type.uiImage { resizeImageToStrSizeSync(image, maxDataSize: 5_000_000) } else { nil } return ThemeWallpaper ( preset: preset, scale: scale, diff --git a/apps/ios/Shared/Theme/ThemeManager.swift b/apps/ios/Shared/Theme/ThemeManager.swift index 9d648750d1..4166619d04 100644 --- a/apps/ios/Shared/Theme/ThemeManager.swift +++ b/apps/ios/Shared/Theme/ThemeManager.swift @@ -197,7 +197,7 @@ class ThemeManager { var themeIds = currentThemeIdsDefault.get() themeIds[nonSystemThemeName] = prevValue.themeId currentThemeIdsDefault.set(themeIds) - applyTheme(nonSystemThemeName) + applyTheme(currentThemeDefault.get()) } static func copyFromSameThemeOverrides(_ type: WallpaperType?, _ lowerLevelOverride: ThemeModeOverride?, _ pref: Binding) -> Bool { diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 97415018bf..ab7a47b944 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -17,8 +17,8 @@ struct ActiveCallView: View { @ObservedObject var call: Call @Environment(\.scenePhase) var scenePhase @State private var client: WebRTCClient? = nil - @State private var activeCall: WebRTCClient.Call? = nil @State private var localRendererAspectRatio: CGFloat? = nil + @State var remoteContentMode: UIView.ContentMode = .scaleAspectFill @Binding var canConnectCall: Bool @State var prevColorScheme: ColorScheme = .dark @State var pipShown = false @@ -27,24 +27,39 @@ struct ActiveCallView: View { var body: some View { ZStack(alignment: .topLeading) { ZStack(alignment: .bottom) { - if let client = client, [call.peerMedia, call.localMedia].contains(.video), activeCall != nil { + if let client = client, call.hasVideo { GeometryReader { g in let width = g.size.width * 0.3 ZStack(alignment: .topTrailing) { - CallViewRemote(client: client, activeCall: $activeCall, activeCallViewIsCollapsed: $m.activeCallViewIsCollapsed, pipShown: $pipShown) - CallViewLocal(client: client, activeCall: $activeCall, localRendererAspectRatio: $localRendererAspectRatio, pipShown: $pipShown) - .cornerRadius(10) - .frame(width: width, height: width / (localRendererAspectRatio ?? 1)) - .padding([.top, .trailing], 17) ZStack(alignment: .center) { // For some reason, when the view in GeometryReader and ZStack is visible, it steals clicks on a back button, so showing something on top like this with background color helps (.clear color doesn't work) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.primary.opacity(0.000001)) + + CallViewRemote(client: client, call: call, activeCallViewIsCollapsed: $m.activeCallViewIsCollapsed, contentMode: $remoteContentMode, pipShown: $pipShown) + .onTapGesture { + remoteContentMode = remoteContentMode == .scaleAspectFill ? .scaleAspectFit : .scaleAspectFill + } + + Group { + let localVideoTrack = client.activeCall?.localVideoTrack ?? client.notConnectedCall?.localCameraAndTrack?.1 + if localVideoTrack != nil { + CallViewLocal(client: client, localRendererAspectRatio: $localRendererAspectRatio, pipShown: $pipShown) + .onDisappear { + localRendererAspectRatio = nil + } + } else { + Rectangle().fill(.black) + } + } + .cornerRadius(10) + .frame(width: width, height: localRendererAspectRatio == nil ? (g.size.width < g.size.height ? width * 1.33 : width / 1.33) : width / (localRendererAspectRatio ?? 1)) + .padding([.top, .trailing], 17) } } } - if let call = m.activeCall, let client = client, (!pipShown || !call.supportsVideo) { + if let call = m.activeCall, let client = client, (!pipShown || !call.hasVideo) { ActiveCallOverlay(call: call, client: client) } } @@ -54,6 +69,9 @@ struct ActiveCallView: View { .onAppear { logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase)), canConnectCall \(canConnectCall)") AppDelegate.keepScreenOn(true) + Task { + await askRequiredPermissions() + } createWebRTCClient() dismissAllSheets() hideKeyboard() @@ -84,7 +102,7 @@ struct ActiveCallView: View { private func createWebRTCClient() { if client == nil && canConnectCall { - client = WebRTCClient($activeCall, { msg in await MainActor.run { processRtcMessage(msg: msg) } }, $localRendererAspectRatio) + client = WebRTCClient({ msg in await MainActor.run { processRtcMessage(msg: msg) } }, $localRendererAspectRatio) Task { await m.callCommand.setClient(client) } @@ -99,7 +117,7 @@ struct ActiveCallView: View { logger.debug("ActiveCallView: response \(msg.resp.respType)") switch msg.resp { case let .capabilities(capabilities): - let callType = CallType(media: call.localMedia, capabilities: capabilities) + let callType = CallType(media: call.initialCallType, capabilities: capabilities) Task { do { try await apiSendCallInvitation(call.contact, callType) @@ -110,7 +128,7 @@ struct ActiveCallView: View { call.callState = .invitationSent call.localCapabilities = capabilities } - if call.supportsVideo && !AVAudioSession.sharedInstance().hasExternalAudioDevice() { + if call.hasVideo && !AVAudioSession.sharedInstance().hasExternalAudioDevice() { try? AVAudioSession.sharedInstance().setCategory(.playback, options: [.allowBluetooth, .allowAirPlay, .allowBluetoothA2DP]) } CallSoundsPlayer.shared.startConnectingCallSound() @@ -120,7 +138,7 @@ struct ActiveCallView: View { Task { do { try await apiSendCallOffer(call.contact, offer, iceCandidates, - media: call.localMedia, capabilities: capabilities) + media: call.initialCallType, capabilities: capabilities) } catch { logger.error("apiSendCallOffer \(responseError(error))") } @@ -164,6 +182,9 @@ struct ActiveCallView: View { } if state.connectionState == "closed" { closeCallView(client) + if let callUUID = m.activeCall?.callUUID { + CallController.shared.endCall(callUUID: callUUID) + } m.activeCall = nil m.activeCallViewIsCollapsed = false } @@ -182,10 +203,18 @@ struct ActiveCallView: View { CallSoundsPlayer.shared.vibrate(long: false) wasConnected = true } + case let .peerMedia(source, enabled): + switch source { + case .mic: call.peerMediaSources.mic = enabled + case .camera: call.peerMediaSources.camera = enabled + case .screenAudio: call.peerMediaSources.screenAudio = enabled + case .screenVideo: call.peerMediaSources.screenVideo = enabled + case .unknown: () + } case .ended: closeCallView(client) call.callState = .ended - if let uuid = call.callkitUUID { + if let uuid = call.callUUID { CallController.shared.endCall(callUUID: uuid) } case .ok: @@ -214,16 +243,38 @@ struct ActiveCallView: View { ChatReceiver.shared.messagesChannel = nil return } - if case let .chatItemStatusUpdated(_, msg) = msg, - msg.chatInfo.id == call.contact.id, - case .sndCall = msg.chatItem.content, - case .sndRcvd = msg.chatItem.meta.itemStatus { + if case let .result(.chatItemsStatusesUpdated(_, chatItems)) = msg, + chatItems.contains(where: { ci in + ci.chatInfo.id == call.contact.id && + ci.chatItem.content.isSndCall && + ci.chatItem.meta.itemStatus.isSndRcvd + }) { CallSoundsPlayer.shared.startInCallSound() ChatReceiver.shared.messagesChannel = nil } } } + private func askRequiredPermissions() async { + let mic = await WebRTCClient.isAuthorized(for: .audio) + await MainActor.run { + call.localMediaSources.mic = mic + } + let cameraAuthorized = AVCaptureDevice.authorizationStatus(for: .video) == .authorized + var camera = call.initialCallType == .audio || cameraAuthorized + if call.initialCallType == .video && !cameraAuthorized { + camera = await WebRTCClient.isAuthorized(for: .video) + await MainActor.run { + if camera, let client { + client.setCameraEnabled(true) + } + } + } + if !mic || !camera { + WebRTCClient.showUnauthorizedAlert(for: !mic ? .audio : .video) + } + } + private func closeCallView(_ client: WebRTCClient) { if m.activeCall != nil { m.showCallView = false @@ -239,44 +290,16 @@ struct ActiveCallOverlay: View { var body: some View { VStack { - switch call.localMedia { - case .video: + switch call.hasVideo { + case true: videoCallInfoView(call) .foregroundColor(.white) .opacity(0.8) - .padding() - - Spacer() - - HStack { - toggleAudioButton() - Spacer() - if deviceManager.availableInputs.allSatisfy({ $0.portType == .builtInMic }) { - toggleSpeakerButton() - .frame(width: 40, height: 40) - } else if call.hasMedia { - AudioDevicePicker() - .scaleEffect(2) - .frame(maxWidth: 40, maxHeight: 40) - } else { - Color.clear.frame(width: 40, height: 40) - } - Spacer() - endCallButton() - Spacer() - if call.videoEnabled { - flipCameraButton() - } else { - Color.clear.frame(width: 40, height: 40) - } - Spacer() - toggleVideoButton() - } - .padding(.horizontal, 20) - .padding(.bottom, 16) - .frame(maxWidth: .infinity, alignment: .center) - - case .audio: + .padding(.horizontal) + // Fixed vertical padding required for preserving position of buttons row when changing audio-to-video and back in landscape orientation. + // Otherwise, bigger padding is added by SwiftUI when switching call types + .padding(.vertical, 10) + case false: ZStack(alignment: .topLeading) { Button { chatModel.activeCallViewIsCollapsed = true @@ -291,35 +314,32 @@ struct ActiveCallOverlay: View { } .foregroundColor(.white) .opacity(0.8) - .padding() + .padding(.horizontal) + .padding(.vertical, 10) .frame(maxHeight: .infinity) } - - Spacer() - - ZStack(alignment: .bottom) { - toggleAudioButton() - .frame(maxWidth: .infinity, alignment: .leading) - endCallButton() - // Check if the only input is microphone. And in this case show toggle button, - // If there are more inputs, it probably means something like bluetooth headphones are available - // and in this case show iOS button for choosing different output. - // There is no way to get available outputs, only inputs - if deviceManager.availableInputs.allSatisfy({ $0.portType == .builtInMic }) { - toggleSpeakerButton() - .frame(maxWidth: .infinity, alignment: .trailing) - } else if call.hasMedia { - AudioDevicePicker() - .scaleEffect(2) - .frame(maxWidth: 50, maxHeight: 40) - .frame(maxWidth: .infinity, alignment: .trailing) - } else { - Color.clear.frame(width: 50, height: 40) - } - } - .padding(.bottom, 60) - .padding(.horizontal, 48) } + + Spacer() + + HStack { + toggleMicButton() + Spacer() + audioDeviceButton() + Spacer() + endCallButton() + Spacer() + if call.localMediaSources.camera { + flipCameraButton() + } else { + Color.clear.frame(width: 60, height: 60) + } + Spacer() + toggleCameraButton() + } + .padding(.horizontal, 20) + .padding(.bottom, 16) + .frame(maxWidth: 440, alignment: .center) } .frame(maxWidth: .infinity) .onAppear { @@ -341,7 +361,7 @@ struct ActiveCallOverlay: View { HStack { Text(call.encryptionStatus) if let connInfo = call.connectionInfo { - Text("(") + Text(connInfo.text) + Text(")") + Text(verbatim: "(") + Text(connInfo.text) + Text(verbatim: ")") } } } @@ -370,7 +390,7 @@ struct ActiveCallOverlay: View { HStack { Text(call.encryptionStatus) if let connInfo = call.connectionInfo { - Text("(") + Text(connInfo.text) + Text(")") + Text(verbatim: "(") + Text(connInfo.text) + Text(verbatim: ")") } } } @@ -381,36 +401,54 @@ struct ActiveCallOverlay: View { private func endCallButton() -> some View { let cc = CallController.shared - return callButton("phone.down.fill", width: 60, height: 60) { - if let uuid = call.callkitUUID { + return callButton("phone.down.fill", .red, padding: 10) { + if let uuid = call.callUUID { cc.endCall(callUUID: uuid) } else { cc.endCall(call: call) {} } } - .foregroundColor(.red) } - private func toggleAudioButton() -> some View { - controlButton(call, call.audioEnabled ? "mic.fill" : "mic.slash") { + private func toggleMicButton() -> some View { + controlButton(call, call.localMediaSources.mic ? "mic.fill" : "mic.slash", padding: 14) { Task { - client.setAudioEnabled(!call.audioEnabled) - DispatchQueue.main.async { - call.audioEnabled = !call.audioEnabled - } + if await WebRTCClient.isAuthorized(for: .audio) { + client.setAudioEnabled(!call.localMediaSources.mic) + } else { WebRTCClient.showUnauthorizedAlert(for: .audio) } + } + } + } + + func audioDeviceButton() -> some View { + // Check if the only input is microphone. And in this case show toggle button, + // If there are more inputs, it probably means something like bluetooth headphones are available + // and in this case show iOS button for choosing different output. + // There is no way to get available outputs, only inputs + Group { + if deviceManager.availableInputs.allSatisfy({ $0.portType == .builtInMic }) { + toggleSpeakerButton() + } else { + audioDevicePickerButton() + } + } + .onChange(of: call.localMediaSources.hasVideo) { hasVideo in + let current = AVAudioSession.sharedInstance().currentRoute.outputs.first?.portType + let speakerEnabled = current == .builtInSpeaker + let receiverEnabled = current == .builtInReceiver + // react automatically only when receiver were selected, otherwise keep an external device selected + if !speakerEnabled && hasVideo && receiverEnabled { + client.setSpeakerEnabledAndConfigureSession(!speakerEnabled, skipExternalDevice: true) + call.speakerEnabled = !speakerEnabled } } } private func toggleSpeakerButton() -> some View { - controlButton(call, call.speakerEnabled ? "speaker.wave.2.fill" : "speaker.wave.1.fill") { - Task { - let speakerEnabled = AVAudioSession.sharedInstance().currentRoute.outputs.first?.portType == .builtInSpeaker - client.setSpeakerEnabledAndConfigureSession(!speakerEnabled) - DispatchQueue.main.async { - call.speakerEnabled = !speakerEnabled - } - } + controlButton(call, !call.peerMediaSources.mic ? "speaker.slash" : call.speakerEnabled ? "speaker.wave.2.fill" : "speaker.wave.1.fill", padding: !call.peerMediaSources.mic ? 16 : call.speakerEnabled ? 15 : 17) { + let speakerEnabled = AVAudioSession.sharedInstance().currentRoute.outputs.first?.portType == .builtInSpeaker + client.setSpeakerEnabledAndConfigureSession(!speakerEnabled) + call.speakerEnabled = !speakerEnabled } .onAppear { deviceManager.call = call @@ -418,53 +456,67 @@ struct ActiveCallOverlay: View { } } - private func toggleVideoButton() -> some View { - controlButton(call, call.videoEnabled ? "video.fill" : "video.slash") { + private func toggleCameraButton() -> some View { + controlButton(call, call.localMediaSources.camera ? "video.fill" : "video.slash", padding: call.localMediaSources.camera ? 16 : 14) { Task { - client.setVideoEnabled(!call.videoEnabled) - DispatchQueue.main.async { - call.videoEnabled = !call.videoEnabled - } + if await WebRTCClient.isAuthorized(for: .video) { + client.setCameraEnabled(!call.localMediaSources.camera) + } else { WebRTCClient.showUnauthorizedAlert(for: .video) } } } + .disabled(call.initialCallType == .audio && client.activeCall?.peerHasOldVersion == true) } - @ViewBuilder private func flipCameraButton() -> some View { - controlButton(call, "arrow.triangle.2.circlepath") { + private func flipCameraButton() -> some View { + controlButton(call, "arrow.triangle.2.circlepath", padding: 12) { Task { - client.flipCamera() + if await WebRTCClient.isAuthorized(for: .video) { + client.flipCamera() + } } } } - @ViewBuilder private func controlButton(_ call: Call, _ imageName: String, _ perform: @escaping () -> Void) -> some View { - if call.hasMedia { - callButton(imageName, width: 50, height: 38, perform) - .foregroundColor(.white) - .opacity(0.85) - } else { - Color.clear.frame(width: 50, height: 38) - } + private func controlButton(_ call: Call, _ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { + callButton(imageName, call.peerMediaSources.hasVideo ? Color.black.opacity(0.2) : Color.white.opacity(0.2), padding: padding, perform) } - private func callButton(_ imageName: String, width: CGFloat, height: CGFloat, _ perform: @escaping () -> Void) -> some View { + private func audioDevicePickerButton() -> some View { + AudioDevicePicker() + .opacity(0.8) + .scaleEffect(2) + .padding(10) + .frame(width: 60, height: 60) + .background(call.peerMediaSources.hasVideo ? Color.black.opacity(0.2) : Color.white.opacity(0.2)) + .clipShape(.circle) + } + + private func callButton(_ imageName: String, _ background: Color, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { Button { perform() } label: { Image(systemName: imageName) .resizable() .scaledToFit() - .frame(maxWidth: width, maxHeight: height) + .padding(padding) + .frame(width: 60, height: 60) + .background(background) } + .foregroundColor(whiteColorWithAlpha) + .clipShape(.circle) + } + + private var whiteColorWithAlpha: Color { + get { Color(red: 204 / 255, green: 204 / 255, blue: 204 / 255) } } } struct ActiveCallOverlay_Previews: PreviewProvider { static var previews: some View { Group{ - ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .video), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) + ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, initialCallType: .video), client: WebRTCClient({ _ in }, Binding.constant(nil))) .background(.black) - ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callkitUUID: UUID(), callState: .offerSent, localMedia: .audio), client: WebRTCClient(Binding.constant(nil), { _ in }, Binding.constant(nil))) + ActiveCallOverlay(call: Call(direction: .incoming, contact: Contact.sampleData, callUUID: UUID().uuidString.lowercased(), callState: .offerSent, initialCallType: .audio), client: WebRTCClient({ _ in }, Binding.constant(nil))) .background(.black) } } diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index a8a91057fa..1f28180e87 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -51,7 +51,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXStartCallAction) { logger.debug("CallController.provider CXStartCallAction") - if callManager.startOutgoingCall(callUUID: action.callUUID) { + if callManager.startOutgoingCall(callUUID: action.callUUID.uuidString.lowercased()) { action.fulfill() provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil) } else { @@ -61,12 +61,30 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { logger.debug("CallController.provider CXAnswerCallAction") - if callManager.answerIncomingCall(callUUID: action.callUUID) { - // WebRTC call should be in connected state to fulfill. - // Otherwise no audio and mic working on lockscreen - fulfillOnConnect = action - } else { - action.fail() + Task { + let chatIsReady = await waitUntilChatStarted(timeoutMs: 30_000, stepMs: 500) + logger.debug("CallController chat started \(chatIsReady) \(ChatModel.shared.chatInitialized) \(ChatModel.shared.chatRunning == true) \(String(describing: AppChatState.shared.value))") + if !chatIsReady { + action.fail() + return + } + if !ChatModel.shared.callInvitations.values.contains(where: { inv in inv.callUUID == action.callUUID.uuidString.lowercased() }) { + try? await justRefreshCallInvitations() + logger.debug("CallController: updated call invitations chat") + } + await MainActor.run { + logger.debug("CallController.provider will answer on call") + + if callManager.answerIncomingCall(callUUID: action.callUUID.uuidString.lowercased()) { + logger.debug("CallController.provider answered on call") + // WebRTC call should be in connected state to fulfill. + // Otherwise no audio and mic working on lockscreen + fulfillOnConnect = action + } else { + logger.debug("CallController.provider will fail the call") + action.fail() + } + } } } @@ -75,7 +93,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse // Should be nil here if connection was in connected state fulfillOnConnect?.fail() fulfillOnConnect = nil - callManager.endCall(callUUID: action.callUUID) { ok in + callManager.endCall(callUUID: action.callUUID.uuidString.lowercased()) { ok in if ok { action.fulfill() } else { @@ -86,7 +104,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { - if callManager.enableMedia(media: .audio, enable: !action.isMuted, callUUID: action.callUUID) { + if callManager.enableMedia(source: .mic, enable: !action.isMuted, callUUID: action.callUUID.uuidString.lowercased()) { action.fulfill() } else { action.fail() @@ -103,8 +121,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession) RTCAudioSession.sharedInstance().isAudioEnabled = true do { - let supportsVideo = ChatModel.shared.activeCall?.supportsVideo == true - if supportsVideo { + let hasVideo = ChatModel.shared.activeCall?.hasVideo == true + if hasVideo { try audioSession.setCategory(.playAndRecord, mode: .videoChat, options: [.defaultToSpeaker, .mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP]) } else { try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP]) @@ -115,7 +133,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse try? await Task.sleep(nanoseconds: UInt64(i) * 300_000000) if let preferred = audioSession.preferredInputDevice() { await MainActor.run { try? audioSession.setPreferredInput(preferred) } - } else if supportsVideo { + } else if hasVideo { await MainActor.run { try? audioSession.overrideOutputAudioPort(.speaker) } } } @@ -156,6 +174,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } } + private func waitUntilChatStarted(timeoutMs: UInt64, stepMs: UInt64) async -> Bool { + logger.debug("CallController waiting until chat started") + var t: UInt64 = 0 + repeat { + if ChatModel.shared.chatInitialized, ChatModel.shared.chatRunning == true, case .active = AppChatState.shared.value { + return true + } + _ = try? await Task.sleep(nanoseconds: stepMs * 1000000) + t += stepMs + } while t < timeoutMs + return false + } + @objc(pushRegistry:didUpdatePushCredentials:forType:) func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)") @@ -171,32 +202,19 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse self.reportExpiredCall(payload: payload, completion) return } - if (!ChatModel.shared.chatInitialized) { - logger.debug("CallController: initializing chat") - do { - try initializeChat(start: true, refreshInvitations: false) - } catch let error { - logger.error("CallController: initializing chat error: \(error)") - self.reportExpiredCall(payload: payload, completion) - return - } - } - logger.debug("CallController: initialized chat") - startChatForCall() - logger.debug("CallController: started chat") - self.shouldSuspendChat = true - // There are no invitations in the model, as it was processed by NSE - try? justRefreshCallInvitations() - logger.debug("CallController: updated call invitations chat") - // logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))") // Extract the call information from the push notification payload let m = ChatModel.shared if let contactId = payload.dictionaryPayload["contactId"] as? String, - let invitation = m.callInvitations[contactId] { - let update = self.cxCallUpdate(invitation: invitation) - if let uuid = invitation.callkitUUID { + let displayName = payload.dictionaryPayload["displayName"] as? String, + let callUUID = payload.dictionaryPayload["callUUID"] as? String, + let uuid = UUID(uuidString: callUUID), + let callTsInterval = payload.dictionaryPayload["callTs"] as? TimeInterval, + let mediaStr = payload.dictionaryPayload["media"] as? String, + let media = CallMediaType(rawValue: mediaStr) { + let update = self.cxCallUpdate(contactId, displayName, media) + let callTs = Date(timeIntervalSince1970: callTsInterval) + if callTs.timeIntervalSinceNow >= -180 { logger.debug("CallController: report pushkit call via CallKit") - let update = self.cxCallUpdate(invitation: invitation) self.provider.reportNewIncomingCall(with: uuid, update: update) { error in if error != nil { m.callInvitations.removeValue(forKey: contactId) @@ -205,11 +223,31 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse completion() } } else { + logger.debug("CallController will expire call 1") self.reportExpiredCall(update: update, completion) } } else { + logger.debug("CallController will expire call 2") self.reportExpiredCall(payload: payload, completion) } + + //DispatchQueue.main.asyncAfter(deadline: .now() + 10) { + if (!ChatModel.shared.chatInitialized) { + logger.debug("CallController: initializing chat") + do { + try initializeChat(start: true, refreshInvitations: false) + } catch let error { + logger.error("CallController: initializing chat error: \(error)") + if let call = ChatModel.shared.activeCall { + self.endCall(call: call, completed: completion) + } + return + } + } + logger.debug("CallController: initialized chat") + startChatForCall() + logger.debug("CallController: started chat") + self.shouldSuspendChat = true } // This function fulfils the requirement to always report a call when PushKit notification is received, @@ -239,8 +277,8 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) { - logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))") - if CallController.useCallKit(), let uuid = invitation.callkitUUID { + logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callUUID))") + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { if invitation.callTs.timeIntervalSinceNow >= -180 { let update = cxCallUpdate(invitation: invitation) provider.reportNewIncomingCall(with: uuid, update: update, completion: completion) @@ -261,6 +299,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse return update } + private func cxCallUpdate(_ contactId: String, _ displayName: String, _ media: CallMediaType) -> CXCallUpdate { + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type: .generic, value: contactId) + update.hasVideo = media == .video + update.localizedCallerName = displayName + return update + } + func reportIncomingCall(call: Call, connectedAt dateConnected: Date?) { logger.debug("CallController: reporting incoming call connected") if CallController.useCallKit() { @@ -272,14 +318,14 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) { logger.debug("CallController: reporting outgoing call connected") - if CallController.useCallKit(), let uuid = call.callkitUUID { + if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected) } } func reportCallRemoteEnded(invitation: RcvCallInvitation) { logger.debug("CallController: reporting remote ended") - if CallController.useCallKit(), let uuid = invitation.callkitUUID { + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) } else if invitation.contact.id == activeCallInvitation?.contact.id { activeCallInvitation = nil @@ -288,14 +334,17 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse func reportCallRemoteEnded(call: Call) { logger.debug("CallController: reporting remote ended") - if CallController.useCallKit(), let uuid = call.callkitUUID { + if CallController.useCallKit(), let callUUID = call.callUUID, let uuid = UUID(uuidString: callUUID) { provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) } } func startCall(_ contact: Contact, _ media: CallMediaType) { logger.debug("CallController.startCall") - let uuid = callManager.newOutgoingCall(contact, media) + let callUUID = callManager.newOutgoingCall(contact, media) + guard let uuid = UUID(uuidString: callUUID) else { + return + } if CallController.useCallKit() { let handle = CXHandle(type: .generic, value: contact.id) let action = CXStartCallAction(call: uuid, handle: handle) @@ -307,19 +356,17 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse update.localizedCallerName = contact.displayName self.provider.reportCall(with: uuid, updated: update) } - } else if callManager.startOutgoingCall(callUUID: uuid) { - if callManager.startOutgoingCall(callUUID: uuid) { - logger.debug("CallController.startCall: call started") - } else { - logger.error("CallController.startCall: no active call") - } + } else if callManager.startOutgoingCall(callUUID: callUUID) { + logger.debug("CallController.startCall: call started") + } else { + logger.error("CallController.startCall: no active call") } } func answerCall(invitation: RcvCallInvitation) { logger.debug("CallController: answering a call") - if CallController.useCallKit(), let callUUID = invitation.callkitUUID { - requestTransaction(with: CXAnswerCallAction(call: callUUID)) + if CallController.useCallKit(), let callUUID = invitation.callUUID, let uuid = UUID(uuidString: callUUID) { + requestTransaction(with: CXAnswerCallAction(call: uuid)) } else { callManager.answerIncomingCall(invitation: invitation) } @@ -328,10 +375,13 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse } } - func endCall(callUUID: UUID) { - logger.debug("CallController: ending the call with UUID \(callUUID.uuidString)") + func endCall(callUUID: String) { + let uuid = UUID(uuidString: callUUID) + logger.debug("CallController: ending the call with UUID \(callUUID)") if CallController.useCallKit() { - requestTransaction(with: CXEndCallAction(call: callUUID)) + if let uuid { + requestTransaction(with: CXEndCallAction(call: uuid)) + } } else { callManager.endCall(callUUID: callUUID) { ok in if ok { diff --git a/apps/ios/Shared/Views/Call/CallManager.swift b/apps/ios/Shared/Views/Call/CallManager.swift index a6d5ea17c4..a3e1df2301 100644 --- a/apps/ios/Shared/Views/Call/CallManager.swift +++ b/apps/ios/Shared/Views/Call/CallManager.swift @@ -10,25 +10,25 @@ import Foundation import SimpleXChat class CallManager { - func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> UUID { - let uuid = UUID() - let call = Call(direction: .outgoing, contact: contact, callkitUUID: uuid, callState: .waitCapabilities, localMedia: media) + func newOutgoingCall(_ contact: Contact, _ media: CallMediaType) -> String { + let uuid = UUID().uuidString.lowercased() + let call = Call(direction: .outgoing, contact: contact, callUUID: uuid, callState: .waitCapabilities, initialCallType: media) call.speakerEnabled = media == .video ChatModel.shared.activeCall = call return uuid } - func startOutgoingCall(callUUID: UUID) -> Bool { + func startOutgoingCall(callUUID: String) -> Bool { let m = ChatModel.shared - if let call = m.activeCall, call.callkitUUID == callUUID { + if let call = m.activeCall, call.callUUID == callUUID { m.showCallView = true - Task { await m.callCommand.processCommand(.capabilities(media: call.localMedia)) } + Task { await m.callCommand.processCommand(.capabilities(media: call.initialCallType)) } return true } return false } - func answerIncomingCall(callUUID: UUID) -> Bool { + func answerIncomingCall(callUUID: String) -> Bool { if let invitation = getCallInvitation(callUUID) { answerIncomingCall(invitation: invitation) return true @@ -42,9 +42,9 @@ class CallManager { let call = Call( direction: .incoming, contact: invitation.contact, - callkitUUID: invitation.callkitUUID, + callUUID: invitation.callUUID, callState: .invitationAccepted, - localMedia: invitation.callType.media, + initialCallType: invitation.callType.media, sharedKey: invitation.sharedKey ) call.speakerEnabled = invitation.callType.media == .video @@ -68,17 +68,17 @@ class CallManager { } } - func enableMedia(media: CallMediaType, enable: Bool, callUUID: UUID) -> Bool { - if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { + func enableMedia(source: CallMediaSource, enable: Bool, callUUID: String) -> Bool { + if let call = ChatModel.shared.activeCall, call.callUUID == callUUID { let m = ChatModel.shared - Task { await m.callCommand.processCommand(.media(media: media, enable: enable)) } + Task { await m.callCommand.processCommand(.media(source: source, enable: enable)) } return true } return false } - func endCall(callUUID: UUID, completed: @escaping (Bool) -> Void) { - if let call = ChatModel.shared.activeCall, call.callkitUUID == callUUID { + func endCall(callUUID: String, completed: @escaping (Bool) -> Void) { + if let call = ChatModel.shared.activeCall, call.callUUID == callUUID { endCall(call: call) { completed(true) } } else if let invitation = getCallInvitation(callUUID) { endCall(invitation: invitation) { completed(true) } @@ -126,8 +126,8 @@ class CallManager { } } - private func getCallInvitation(_ callUUID: UUID) -> RcvCallInvitation? { - if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callkitUUID == callUUID }) { + private func getCallInvitation(_ callUUID: String) -> RcvCallInvitation? { + if let (_, invitation) = ChatModel.shared.callInvitations.first(where: { (_, inv) in inv.callUUID == callUUID }) { return invitation } return nil diff --git a/apps/ios/Shared/Views/Call/CallViewRenderers.swift b/apps/ios/Shared/Views/Call/CallViewRenderers.swift index a3201d9351..e779093a24 100644 --- a/apps/ios/Shared/Views/Call/CallViewRenderers.swift +++ b/apps/ios/Shared/Views/Call/CallViewRenderers.swift @@ -10,40 +10,49 @@ import AVKit struct CallViewRemote: UIViewRepresentable { var client: WebRTCClient - var activeCall: Binding + @ObservedObject var call: Call @State var enablePip: (Bool) -> Void = {_ in } @Binding var activeCallViewIsCollapsed: Bool + @Binding var contentMode: UIView.ContentMode @Binding var pipShown: Bool - init(client: WebRTCClient, activeCall: Binding, activeCallViewIsCollapsed: Binding, pipShown: Binding) { - self.client = client - self.activeCall = activeCall - self._activeCallViewIsCollapsed = activeCallViewIsCollapsed - self._pipShown = pipShown - } - func makeUIView(context: Context) -> UIView { let view = UIView() - if let call = activeCall.wrappedValue { - let remoteRenderer = RTCMTLVideoView(frame: view.frame) - remoteRenderer.videoContentMode = .scaleAspectFill - client.addRemoteRenderer(call, remoteRenderer) - addSubviewAndResize(remoteRenderer, into: view) + let remoteCameraRenderer = RTCMTLVideoView(frame: view.frame) + remoteCameraRenderer.videoContentMode = contentMode + remoteCameraRenderer.tag = 0 - if AVPictureInPictureController.isPictureInPictureSupported() { - makeViewWithRTCRenderer(call, remoteRenderer, view, context) - } + let screenVideo = call.peerMediaSources.screenVideo + let remoteScreenRenderer = RTCMTLVideoView(frame: view.frame) + remoteScreenRenderer.videoContentMode = contentMode + remoteScreenRenderer.tag = 1 + remoteScreenRenderer.alpha = screenVideo ? 1 : 0 + + context.coordinator.cameraRenderer = remoteCameraRenderer + context.coordinator.screenRenderer = remoteScreenRenderer + client.addRemoteCameraRenderer(remoteCameraRenderer) + client.addRemoteScreenRenderer(remoteScreenRenderer) + if screenVideo { + addSubviewAndResize(remoteScreenRenderer, remoteCameraRenderer, into: view) + } else { + addSubviewAndResize(remoteCameraRenderer, remoteScreenRenderer, into: view) + } + + if AVPictureInPictureController.isPictureInPictureSupported() { + makeViewWithRTCRenderer(remoteCameraRenderer, remoteScreenRenderer, view, context) } return view } - func makeViewWithRTCRenderer(_ call: WebRTCClient.Call, _ remoteRenderer: RTCMTLVideoView, _ view: UIView, _ context: Context) { - let pipRemoteRenderer = RTCMTLVideoView(frame: view.frame) - pipRemoteRenderer.videoContentMode = .scaleAspectFill - + func makeViewWithRTCRenderer(_ remoteCameraRenderer: RTCMTLVideoView, _ remoteScreenRenderer: RTCMTLVideoView, _ view: UIView, _ context: Context) { + let pipRemoteCameraRenderer = RTCMTLVideoView(frame: view.frame) + pipRemoteCameraRenderer.videoContentMode = .scaleAspectFill + + let pipRemoteScreenRenderer = RTCMTLVideoView(frame: view.frame) + pipRemoteScreenRenderer.videoContentMode = .scaleAspectFill + let pipVideoCallViewController = AVPictureInPictureVideoCallViewController() pipVideoCallViewController.preferredContentSize = CGSize(width: 1080, height: 1920) - addSubviewAndResize(pipRemoteRenderer, into: pipVideoCallViewController.view) let pipContentSource = AVPictureInPictureController.ContentSource( activeVideoCallSourceView: view, contentViewController: pipVideoCallViewController @@ -55,7 +64,9 @@ struct CallViewRemote: UIViewRepresentable { context.coordinator.pipController = pipController context.coordinator.willShowHide = { show in if show { - client.addRemoteRenderer(call, pipRemoteRenderer) + client.addRemoteCameraRenderer(pipRemoteCameraRenderer) + client.addRemoteScreenRenderer(pipRemoteScreenRenderer) + context.coordinator.relayout() DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { activeCallViewIsCollapsed = true } @@ -67,13 +78,29 @@ struct CallViewRemote: UIViewRepresentable { } context.coordinator.didShowHide = { show in if show { - remoteRenderer.isHidden = true + remoteCameraRenderer.isHidden = true + remoteScreenRenderer.isHidden = true } else { - client.removeRemoteRenderer(call, pipRemoteRenderer) - remoteRenderer.isHidden = false + client.removeRemoteCameraRenderer(pipRemoteCameraRenderer) + client.removeRemoteScreenRenderer(pipRemoteScreenRenderer) + remoteCameraRenderer.isHidden = false + remoteScreenRenderer.isHidden = false } pipShown = show } + context.coordinator.relayout = { + let camera = call.peerMediaSources.camera + let screenVideo = call.peerMediaSources.screenVideo + pipRemoteCameraRenderer.alpha = camera ? 1 : 0 + pipRemoteScreenRenderer.alpha = screenVideo ? 1 : 0 + if screenVideo { + addSubviewAndResize(pipRemoteScreenRenderer, pipRemoteCameraRenderer, pip: true, into: pipVideoCallViewController.view) + } else { + addSubviewAndResize(pipRemoteCameraRenderer, pipRemoteScreenRenderer, pip: true, into: pipVideoCallViewController.view) + } + (pipVideoCallViewController.view.subviews[0] as! RTCMTLVideoView).videoContentMode = contentMode + (pipVideoCallViewController.view.subviews[1] as! RTCMTLVideoView).videoContentMode = .scaleAspectFill + } DispatchQueue.main.async { enablePip = { enable in if enable != pipShown /* pipController.isPictureInPictureActive */ { @@ -88,24 +115,50 @@ struct CallViewRemote: UIViewRepresentable { } func makeCoordinator() -> Coordinator { - Coordinator() + Coordinator(client) } func updateUIView(_ view: UIView, context: Context) { logger.debug("CallView.updateUIView remote") + let camera = view.subviews.first(where: { $0.tag == 0 })! + let screen = view.subviews.first(where: { $0.tag == 1 })! + let screenVideo = call.peerMediaSources.screenVideo + if screenVideo && screen.alpha == 0 { + screen.alpha = 1 + addSubviewAndResize(screen, camera, into: view) + } else if !screenVideo && screen.alpha == 1 { + screen.alpha = 0 + addSubviewAndResize(camera, screen, into: view) + } + (view.subviews[0] as! RTCMTLVideoView).videoContentMode = contentMode + (view.subviews[1] as! RTCMTLVideoView).videoContentMode = .scaleAspectFill + + camera.alpha = call.peerMediaSources.camera ? 1 : 0 + screen.alpha = call.peerMediaSources.screenVideo ? 1 : 0 + DispatchQueue.main.async { if activeCallViewIsCollapsed != pipShown { enablePip(activeCallViewIsCollapsed) + } else if pipShown { + context.coordinator.relayout() } } } // MARK: - Coordinator class Coordinator: NSObject, AVPictureInPictureControllerDelegate { + var cameraRenderer: RTCMTLVideoView? + var screenRenderer: RTCMTLVideoView? + var client: WebRTCClient var pipController: AVPictureInPictureController? = nil var willShowHide: (Bool) -> Void = { _ in } var didShowHide: (Bool) -> Void = { _ in } - + var relayout: () -> Void = {} + + required init(_ client: WebRTCClient) { + self.client = client + } + func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { willShowHide(true) } @@ -127,11 +180,20 @@ struct CallViewRemote: UIViewRepresentable { } deinit { + // TODO: deinit is not called when changing call type from audio to video and back, + // which causes many renderers can be created and added to stream (if enabling/disabling + // video while not yet connected in outgoing call) pipController?.stopPictureInPicture() pipController?.canStartPictureInPictureAutomaticallyFromInline = false pipController?.contentSource = nil pipController?.delegate = nil pipController = nil + if let cameraRenderer { + client.removeRemoteCameraRenderer(cameraRenderer) + } + if let screenRenderer { + client.removeRemoteScreenRenderer(screenRenderer) + } } } @@ -148,51 +210,109 @@ struct CallViewRemote: UIViewRepresentable { struct CallViewLocal: UIViewRepresentable { var client: WebRTCClient - var activeCall: Binding var localRendererAspectRatio: Binding @State var pipStateChanged: (Bool) -> Void = {_ in } @Binding var pipShown: Bool - init(client: WebRTCClient, activeCall: Binding, localRendererAspectRatio: Binding, pipShown: Binding) { + init(client: WebRTCClient, localRendererAspectRatio: Binding, pipShown: Binding) { self.client = client - self.activeCall = activeCall self.localRendererAspectRatio = localRendererAspectRatio self._pipShown = pipShown } func makeUIView(context: Context) -> UIView { let view = UIView() - if let call = activeCall.wrappedValue { - let localRenderer = RTCEAGLVideoView(frame: .zero) - client.addLocalRenderer(call, localRenderer) - client.startCaptureLocalVideo(call) - addSubviewAndResize(localRenderer, into: view) - DispatchQueue.main.async { - pipStateChanged = { shown in - localRenderer.isHidden = shown - } + let localRenderer = RTCEAGLVideoView(frame: .zero) + context.coordinator.renderer = localRenderer + client.addLocalRenderer(localRenderer) + addSubviewAndResize(localRenderer, nil, into: view) + DispatchQueue.main.async { + pipStateChanged = { shown in + localRenderer.isHidden = shown } } return view } + func makeCoordinator() -> Coordinator { + Coordinator(client) + } + func updateUIView(_ view: UIView, context: Context) { logger.debug("CallView.updateUIView local") pipStateChanged(pipShown) } + + // MARK: - Coordinator + class Coordinator: NSObject, AVPictureInPictureControllerDelegate { + var renderer: RTCEAGLVideoView? + var client: WebRTCClient + + required init(_ client: WebRTCClient) { + self.client = client + } + + deinit { + if let renderer { + client.removeLocalRenderer(renderer) + } + } + } } -private func addSubviewAndResize(_ view: UIView, into containerView: UIView) { - containerView.addSubview(view) - view.translatesAutoresizingMaskIntoConstraints = false - containerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|", - options: [], - metrics: nil, - views: ["view": view])) +private func addSubviewAndResize(_ fullscreen: UIView, _ end: UIView?, pip: Bool = false, into containerView: UIView) { + if containerView.subviews.firstIndex(of: fullscreen) == 0 && ((end == nil && containerView.subviews.count == 1) || (end != nil && containerView.subviews.firstIndex(of: end!) == 1)) { + // Nothing to do, elements on their places + return + } + containerView.removeConstraints(containerView.constraints) + containerView.subviews.forEach { sub in sub.removeFromSuperview()} - containerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|", + containerView.addSubview(fullscreen) + fullscreen.translatesAutoresizingMaskIntoConstraints = false + fullscreen.layer.cornerRadius = 0 + fullscreen.layer.masksToBounds = false + + if let end { + containerView.addSubview(end) + end.translatesAutoresizingMaskIntoConstraints = false + end.layer.cornerRadius = pip ? 8 : 10 + end.layer.masksToBounds = true + } + + let constraintFullscreenV = NSLayoutConstraint.constraints( + withVisualFormat: "V:|[fullscreen]|", options: [], metrics: nil, - views: ["view": view])) + views: ["fullscreen": fullscreen] + ) + let constraintFullscreenH = NSLayoutConstraint.constraints( + withVisualFormat: "H:|[fullscreen]|", + options: [], + metrics: nil, + views: ["fullscreen": fullscreen] + ) + + containerView.addConstraints(constraintFullscreenV) + containerView.addConstraints(constraintFullscreenH) + + if let end { + let constraintEndWidth = NSLayoutConstraint( + item: end, attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: pip ? 0.5 : 0.3, constant: 0 + ) + let constraintEndHeight = NSLayoutConstraint( + item: end, attribute: .height, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: pip ? 0.5 * 1.33 : 0.3 * 1.33, constant: 0 + ) + let constraintEndX = NSLayoutConstraint( + item: end, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .trailing, multiplier: pip ? 0.5 : 0.7, constant: pip ? -8 : -17 + ) + let constraintEndY = NSLayoutConstraint( + item: end, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: pip ? -8 : -92 + ) + containerView.addConstraint(constraintEndWidth) + containerView.addConstraint(constraintEndHeight) + containerView.addConstraint(constraintEndX) + containerView.addConstraint(constraintEndY) + } containerView.layoutIfNeeded() } diff --git a/apps/ios/Shared/Views/Call/IncomingCallView.swift b/apps/ios/Shared/Views/Call/IncomingCallView.swift index 4960281d72..5479a9fada 100644 --- a/apps/ios/Shared/Views/Call/IncomingCallView.swift +++ b/apps/ios/Shared/Views/Call/IncomingCallView.swift @@ -38,6 +38,7 @@ struct IncomingCallView: View { } HStack { ProfilePreview(profileOf: invitation.contact, color: .white) + .padding(.vertical, 6) Spacer() callButton("Reject", "phone.down.fill", .red) { diff --git a/apps/ios/Shared/Views/Call/WebRTC.swift b/apps/ios/Shared/Views/Call/WebRTC.swift index 333dc082d5..ef9135761c 100644 --- a/apps/ios/Shared/Views/Call/WebRTC.swift +++ b/apps/ios/Shared/Views/Call/WebRTC.swift @@ -18,49 +18,49 @@ class Call: ObservableObject, Equatable { var direction: CallDirection var contact: Contact - var callkitUUID: UUID? - var localMedia: CallMediaType + var callUUID: String? + var initialCallType: CallMediaType + @Published var localMediaSources: CallMediaSources @Published var callState: CallState @Published var localCapabilities: CallCapabilities? - @Published var peerMedia: CallMediaType? + @Published var peerMediaSources: CallMediaSources = CallMediaSources() @Published var sharedKey: String? - @Published var audioEnabled = true @Published var speakerEnabled = false - @Published var videoEnabled: Bool @Published var connectionInfo: ConnectionInfo? @Published var connectedAt: Date? = nil init( direction: CallDirection, contact: Contact, - callkitUUID: UUID?, + callUUID: String?, callState: CallState, - localMedia: CallMediaType, + initialCallType: CallMediaType, sharedKey: String? = nil ) { self.direction = direction self.contact = contact - self.callkitUUID = callkitUUID + self.callUUID = callUUID self.callState = callState - self.localMedia = localMedia + self.initialCallType = initialCallType self.sharedKey = sharedKey - self.videoEnabled = localMedia == .video + self.localMediaSources = CallMediaSources( + mic: AVCaptureDevice.authorizationStatus(for: .audio) == .authorized, + camera: initialCallType == .video && AVCaptureDevice.authorizationStatus(for: .video) == .authorized) } var encrypted: Bool { get { localEncrypted && sharedKey != nil } } - var localEncrypted: Bool { get { localCapabilities?.encryption ?? false } } + private var localEncrypted: Bool { get { localCapabilities?.encryption ?? false } } var encryptionStatus: LocalizedStringKey { get { switch callState { case .waitCapabilities: return "" case .invitationSent: return localEncrypted ? "e2e encrypted" : "no e2e encryption" case .invitationAccepted: return sharedKey == nil ? "contact has no e2e encryption" : "contact has e2e encryption" - default: return !localEncrypted ? "no e2e encryption" : sharedKey == nil ? "contact has no e2e encryption" : "e2e encrypted" + default: return !localEncrypted ? "no e2e encryption" : sharedKey == nil ? "contact has no e2e encryption" : "e2e encrypted" } } } - var hasMedia: Bool { get { callState == .offerSent || callState == .negotiated || callState == .connected } } - var supportsVideo: Bool { get { peerMedia == .video || localMedia == .video } } + var hasVideo: Bool { get { localMediaSources.hasVideo || peerMediaSources.hasVideo } } } enum CallDirection { @@ -105,18 +105,28 @@ struct WVAPIMessage: Equatable, Decodable, Encodable { var command: WCallCommand? } +struct CallMediaSources: Equatable, Codable { + var mic: Bool = false + var camera: Bool = false + var screenAudio: Bool = false + var screenVideo: Bool = false + + var hasVideo: Bool { get { camera || screenVideo } } +} + enum WCallCommand: Equatable, Encodable, Decodable { case capabilities(media: CallMediaType) case start(media: CallMediaType, aesKey: String? = nil, iceServers: [RTCIceServer]? = nil, relay: Bool? = nil) case offer(offer: String, iceCandidates: String, media: CallMediaType, aesKey: String? = nil, iceServers: [RTCIceServer]? = nil, relay: Bool? = nil) case answer(answer: String, iceCandidates: String) case ice(iceCandidates: String) - case media(media: CallMediaType, enable: Bool) + case media(source: CallMediaSource, enable: Bool) case end enum CodingKeys: String, CodingKey { case type case media + case source case aesKey case offer case answer @@ -167,9 +177,9 @@ enum WCallCommand: Equatable, Encodable, Decodable { case let .ice(iceCandidates): try container.encode("ice", forKey: .type) try container.encode(iceCandidates, forKey: .iceCandidates) - case let .media(media, enable): + case let .media(source, enable): try container.encode("media", forKey: .type) - try container.encode(media, forKey: .media) + try container.encode(source, forKey: .media) try container.encode(enable, forKey: .enable) case .end: try container.encode("end", forKey: .type) @@ -205,9 +215,9 @@ enum WCallCommand: Equatable, Encodable, Decodable { let iceCandidates = try container.decode(String.self, forKey: CodingKeys.iceCandidates) self = .ice(iceCandidates: iceCandidates) case "media": - let media = try container.decode(CallMediaType.self, forKey: CodingKeys.media) + let source = try container.decode(CallMediaSource.self, forKey: CodingKeys.source) let enable = try container.decode(Bool.self, forKey: CodingKeys.enable) - self = .media(media: media, enable: enable) + self = .media(source: source, enable: enable) case "end": self = .end default: @@ -224,6 +234,7 @@ enum WCallResponse: Equatable, Decodable { case ice(iceCandidates: String) case connection(state: ConnectionState) case connected(connectionInfo: ConnectionInfo) + case peerMedia(source: CallMediaSource, enabled: Bool) case ended case ok case error(message: String) @@ -238,6 +249,8 @@ enum WCallResponse: Equatable, Decodable { case state case connectionInfo case message + case source + case enabled } var respType: String { @@ -249,6 +262,7 @@ enum WCallResponse: Equatable, Decodable { case .ice: return "ice" case .connection: return "connection" case .connected: return "connected" + case .peerMedia: return "peerMedia" case .ended: return "ended" case .ok: return "ok" case .error: return "error" @@ -283,6 +297,10 @@ enum WCallResponse: Equatable, Decodable { case "connected": let connectionInfo = try container.decode(ConnectionInfo.self, forKey: CodingKeys.connectionInfo) self = .connected(connectionInfo: connectionInfo) + case "peerMedia": + let source = try container.decode(CallMediaSource.self, forKey: CodingKeys.source) + let enabled = try container.decode(Bool.self, forKey: CodingKeys.enabled) + self = .peerMedia(source: source, enabled: enabled) case "ended": self = .ended case "ok": @@ -324,6 +342,10 @@ extension WCallResponse: Encodable { case let .connected(connectionInfo): try container.encode("connected", forKey: .type) try container.encode(connectionInfo, forKey: .connectionInfo) + case let .peerMedia(source, enabled): + try container.encode("peerMedia", forKey: .type) + try container.encode(source, forKey: .source) + try container.encode(enabled, forKey: .enabled) case .ended: try container.encode("ended", forKey: .type) case .ok: @@ -376,7 +398,7 @@ actor WebRTCCommandProcessor { func shouldRunCommand(_ client: WebRTCClient, _ c: WCallCommand) -> Bool { switch c { case .capabilities, .start, .offer, .end: true - default: client.activeCall.wrappedValue != nil + default: client.activeCall != nil } } } diff --git a/apps/ios/Shared/Views/Call/WebRTCClient.swift b/apps/ios/Shared/Views/Call/WebRTCClient.swift index 79e0dea16c..db7910836e 100644 --- a/apps/ios/Shared/Views/Call/WebRTCClient.swift +++ b/apps/ios/Shared/Views/Call/WebRTCClient.swift @@ -23,15 +23,24 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg struct Call { var connection: RTCPeerConnection var iceCandidates: IceCandidates - var localMedia: CallMediaType var localCamera: RTCVideoCapturer? - var localVideoSource: RTCVideoSource? - var localStream: RTCVideoTrack? - var remoteStream: RTCVideoTrack? - var device: AVCaptureDevice.Position = .front + var localAudioTrack: RTCAudioTrack? + var localVideoTrack: RTCVideoTrack? + var remoteAudioTrack: RTCAudioTrack? + var remoteVideoTrack: RTCVideoTrack? + var remoteScreenAudioTrack: RTCAudioTrack? + var remoteScreenVideoTrack: RTCVideoTrack? + var device: AVCaptureDevice.Position var aesKey: String? var frameEncryptor: RTCFrameEncryptor? var frameDecryptor: RTCFrameDecryptor? + var peerHasOldVersion: Bool + } + + struct NotConnectedCall { + var audioTrack: RTCAudioTrack? + var localCameraAndTrack: (RTCVideoCapturer, RTCVideoTrack)? + var device: AVCaptureDevice.Position = .front } actor IceCandidates { @@ -51,17 +60,20 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg private let rtcAudioSession = RTCAudioSession.sharedInstance() private let audioQueue = DispatchQueue(label: "chat.simplex.app.audio") private var sendCallResponse: (WVAPIMessage) async -> Void - var activeCall: Binding + var activeCall: Call? + var notConnectedCall: NotConnectedCall? private var localRendererAspectRatio: Binding + var cameraRenderers: [RTCVideoRenderer] = [] + var screenRenderers: [RTCVideoRenderer] = [] + @available(*, unavailable) override init() { fatalError("Unimplemented") } - required init(_ activeCall: Binding, _ sendCallResponse: @escaping (WVAPIMessage) async -> Void, _ localRendererAspectRatio: Binding) { + required init(_ sendCallResponse: @escaping (WVAPIMessage) async -> Void, _ localRendererAspectRatio: Binding) { self.sendCallResponse = sendCallResponse - self.activeCall = activeCall self.localRendererAspectRatio = localRendererAspectRatio rtcAudioSession.useManualAudio = CallController.useCallKit() rtcAudioSession.isAudioEnabled = !CallController.useCallKit() @@ -78,39 +90,45 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg func initializeCall(_ iceServers: [WebRTC.RTCIceServer]?, _ mediaType: CallMediaType, _ aesKey: String?, _ relay: Bool?) -> Call { let connection = createPeerConnection(iceServers ?? getWebRTCIceServers() ?? defaultIceServers, relay) connection.delegate = self - createAudioSender(connection) - var localStream: RTCVideoTrack? = nil - var remoteStream: RTCVideoTrack? = nil + let device = notConnectedCall?.device ?? .front var localCamera: RTCVideoCapturer? = nil - var localVideoSource: RTCVideoSource? = nil - if mediaType == .video { - (localStream, remoteStream, localCamera, localVideoSource) = createVideoSender(connection) + var localAudioTrack: RTCAudioTrack? = nil + var localVideoTrack: RTCVideoTrack? = nil + if let localCameraAndTrack = notConnectedCall?.localCameraAndTrack { + (localCamera, localVideoTrack) = localCameraAndTrack + } else if notConnectedCall == nil && mediaType == .video { + (localCamera, localVideoTrack) = createVideoTrackAndStartCapture(device) } + if let audioTrack = notConnectedCall?.audioTrack { + localAudioTrack = audioTrack + } else if notConnectedCall == nil { + localAudioTrack = createAudioTrack() + } + notConnectedCall?.localCameraAndTrack = nil + notConnectedCall?.audioTrack = nil + var frameEncryptor: RTCFrameEncryptor? = nil var frameDecryptor: RTCFrameDecryptor? = nil if aesKey != nil { let encryptor = RTCFrameEncryptor.init(sizeChange: Int32(WebRTCClient.ivTagBytes)) encryptor.delegate = self frameEncryptor = encryptor - connection.senders.forEach { $0.setRtcFrameEncryptor(encryptor) } let decryptor = RTCFrameDecryptor.init(sizeChange: -Int32(WebRTCClient.ivTagBytes)) decryptor.delegate = self frameDecryptor = decryptor - // Has no video receiver in outgoing call if applied here, see [peerConnection(_ connection: RTCPeerConnection, didChange newState] - // connection.receivers.forEach { $0.setRtcFrameDecryptor(decryptor) } } return Call( connection: connection, iceCandidates: IceCandidates(), - localMedia: mediaType, localCamera: localCamera, - localVideoSource: localVideoSource, - localStream: localStream, - remoteStream: remoteStream, + localAudioTrack: localAudioTrack, + localVideoTrack: localVideoTrack, + device: device, aesKey: aesKey, frameEncryptor: frameEncryptor, - frameDecryptor: frameDecryptor + frameDecryptor: frameDecryptor, + peerHasOldVersion: false ) } @@ -151,18 +169,24 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg func sendCallCommand(command: WCallCommand) async { var resp: WCallResponse? = nil - let pc = activeCall.wrappedValue?.connection + let pc = activeCall?.connection switch command { - case .capabilities: + case let .capabilities(media): // outgoing + let localCameraAndTrack: (RTCVideoCapturer, RTCVideoTrack)? = media == .video + ? createVideoTrackAndStartCapture(.front) + : nil + notConnectedCall = NotConnectedCall(audioTrack: createAudioTrack(), localCameraAndTrack: localCameraAndTrack, device: .front) resp = .capabilities(capabilities: CallCapabilities(encryption: WebRTCClient.enableEncryption)) - case let .start(media: media, aesKey, iceServers, relay): + case let .start(media: media, aesKey, iceServers, relay): // incoming logger.debug("starting incoming call - create webrtc session") - if activeCall.wrappedValue != nil { endCall() } + if activeCall != nil { endCall() } let encryption = WebRTCClient.enableEncryption let call = initializeCall(iceServers?.toWebRTCIceServers(), media, encryption ? aesKey : nil, relay) - activeCall.wrappedValue = call + activeCall = call + setupLocalTracks(true, call) let (offer, error) = await call.connection.offer() if let offer = offer { + setupEncryptionForLocalTracks(call) resp = .offer( offer: compressToBase64(input: encodeJSON(CustomRTCSessionDescription(type: offer.type.toSdpType(), sdp: offer.sdp))), iceCandidates: compressToBase64(input: encodeJSON(await self.getInitialIceCandidates())), @@ -172,18 +196,24 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg } else { resp = .error(message: "offer error: \(error?.localizedDescription ?? "unknown error")") } - case let .offer(offer, iceCandidates, media, aesKey, iceServers, relay): - if activeCall.wrappedValue != nil { + case let .offer(offer, iceCandidates, media, aesKey, iceServers, relay): // outgoing + if activeCall != nil { resp = .error(message: "accept: call already started") } else if !WebRTCClient.enableEncryption && aesKey != nil { resp = .error(message: "accept: encryption is not supported") } else if let offer: CustomRTCSessionDescription = decodeJSON(decompressFromBase64(input: offer)), let remoteIceCandidates: [RTCIceCandidate] = decodeJSON(decompressFromBase64(input: iceCandidates)) { let call = initializeCall(iceServers?.toWebRTCIceServers(), media, WebRTCClient.enableEncryption ? aesKey : nil, relay) - activeCall.wrappedValue = call + activeCall = call let pc = call.connection if let type = offer.type, let sdp = offer.sdp { if (try? await pc.setRemoteDescription(RTCSessionDescription(type: type.toWebRTCSdpType(), sdp: sdp))) != nil { + setupLocalTracks(false, call) + setupEncryptionForLocalTracks(call) + pc.transceivers.forEach { transceiver in + transceiver.setDirection(.sendRecv, error: nil) + } + await adaptToOldVersion(pc.transceivers.count <= 2) let (answer, error) = await pc.answer() if let answer = answer { self.addIceCandidates(pc, remoteIceCandidates) @@ -200,7 +230,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg } } } - case let .answer(answer, iceCandidates): + case let .answer(answer, iceCandidates): // incoming if pc == nil { resp = .error(message: "answer: call not started") } else if pc?.localDescription == nil { @@ -212,6 +242,9 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg let type = answer.type, let sdp = answer.sdp, let pc = pc { if (try? await pc.setRemoteDescription(RTCSessionDescription(type: type.toWebRTCSdpType(), sdp: sdp))) != nil { + var currentDirection: RTCRtpTransceiverDirection = .sendOnly + pc.transceivers[2].currentDirection(¤tDirection) + await adaptToOldVersion(currentDirection == .sendOnly) addIceCandidates(pc, remoteIceCandidates) resp = .ok } else { @@ -226,13 +259,11 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg } else { resp = .error(message: "ice: call not started") } - case let .media(media, enable): - if activeCall.wrappedValue == nil { + case let .media(source, enable): + if activeCall == nil { resp = .error(message: "media: call not started") - } else if activeCall.wrappedValue?.localMedia == .audio && media == .video { - resp = .error(message: "media: no video") } else { - enableMedia(media, enable) + await enableMedia(source, enable) resp = .ok } case .end: @@ -247,7 +278,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg func getInitialIceCandidates() async -> [RTCIceCandidate] { await untilIceComplete(timeoutMs: 750, stepMs: 150) {} - let candidates = await activeCall.wrappedValue?.iceCandidates.getAndClear() ?? [] + let candidates = await activeCall?.iceCandidates.getAndClear() ?? [] logger.debug("WebRTCClient: sending initial ice candidates: \(candidates.count)") return candidates } @@ -255,7 +286,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg func waitForMoreIceCandidates() { Task { await untilIceComplete(timeoutMs: 12000, stepMs: 1500) { - let candidates = await self.activeCall.wrappedValue?.iceCandidates.getAndClear() ?? [] + let candidates = await self.activeCall?.iceCandidates.getAndClear() ?? [] if candidates.count > 0 { logger.debug("WebRTCClient: sending more ice candidates: \(candidates.count)") await self.sendIceCandidates(candidates) @@ -272,25 +303,202 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg ) } - func enableMedia(_ media: CallMediaType, _ enable: Bool) { - logger.debug("WebRTCClient: enabling media \(media.rawValue) \(enable)") - media == .video ? setVideoEnabled(enable) : setAudioEnabled(enable) + func setupMuteUnmuteListener(_ transceiver: RTCRtpTransceiver, _ track: RTCMediaStreamTrack) { + // logger.log("Setting up mute/unmute listener in the call without encryption for mid = \(transceiver.mid)") + Task { + var lastBytesReceived: Int64 = 0 + // muted initially + var mutedSeconds = 4 + while let call = self.activeCall, transceiver.receiver.track?.readyState == .live { + let stats: RTCStatisticsReport = await call.connection.statistics(for: transceiver.receiver) + let stat = stats.statistics.values.first(where: { stat in stat.type == "inbound-rtp"}) + if let stat { + //logger.debug("Stat \(stat.debugDescription)") + let bytes = stat.values["bytesReceived"] as! Int64 + if bytes <= lastBytesReceived { + mutedSeconds += 1 + if mutedSeconds == 3 { + await MainActor.run { + self.onMediaMuteUnmute(transceiver.mid, true) + } + } + } else { + if mutedSeconds >= 3 { + await MainActor.run { + self.onMediaMuteUnmute(transceiver.mid, false) + } + } + lastBytesReceived = bytes + mutedSeconds = 0 + } + } + try? await Task.sleep(nanoseconds: 1000_000000) + } + } } - func addLocalRenderer(_ activeCall: Call, _ renderer: RTCEAGLVideoView) { - activeCall.localStream?.add(renderer) + @MainActor + func onMediaMuteUnmute(_ transceiverMid: String?, _ mute: Bool) { + guard let activeCall = ChatModel.shared.activeCall else { return } + let source = mediaSourceFromTransceiverMid(transceiverMid) + logger.log("Mute/unmute \(source.rawValue) track = \(mute) with mid = \(transceiverMid ?? "nil")") + if source == .mic && activeCall.peerMediaSources.mic == mute { + activeCall.peerMediaSources.mic = !mute + } else if (source == .camera && activeCall.peerMediaSources.camera == mute) { + activeCall.peerMediaSources.camera = !mute + } else if (source == .screenAudio && activeCall.peerMediaSources.screenAudio == mute) { + activeCall.peerMediaSources.screenAudio = !mute + } else if (source == .screenVideo && activeCall.peerMediaSources.screenVideo == mute) { + activeCall.peerMediaSources.screenVideo = !mute + } + } + + @MainActor + func enableMedia(_ source: CallMediaSource, _ enable: Bool) { + logger.debug("WebRTCClient: enabling media \(source.rawValue) \(enable)") + source == .camera ? setCameraEnabled(enable) : setAudioEnabled(enable) + } + + @MainActor + func adaptToOldVersion(_ peerHasOldVersion: Bool) { + activeCall?.peerHasOldVersion = peerHasOldVersion + if peerHasOldVersion { + logger.debug("The peer has an old version. Remote audio track is nil = \(self.activeCall?.remoteAudioTrack == nil), video = \(self.activeCall?.remoteVideoTrack == nil)") + onMediaMuteUnmute("0", false) + if activeCall?.remoteVideoTrack != nil { + onMediaMuteUnmute("1", false) + } + if ChatModel.shared.activeCall?.localMediaSources.camera == true && ChatModel.shared.activeCall?.peerMediaSources.camera == false { + logger.debug("Stopping video track for the old version") + activeCall?.connection.senders[1].track = nil + ChatModel.shared.activeCall?.localMediaSources.camera = false + (activeCall?.localCamera as? RTCCameraVideoCapturer)?.stopCapture() + activeCall?.localCamera = nil + activeCall?.localVideoTrack = nil + } + } + } + + func addLocalRenderer(_ renderer: RTCEAGLVideoView) { + if let activeCall { + if let track = activeCall.localVideoTrack { + track.add(renderer) + } + } else if let notConnectedCall { + if let track = notConnectedCall.localCameraAndTrack?.1 { + track.add(renderer) + } + } // To get width and height of a frame, see videoView(videoView:, didChangeVideoSize) renderer.delegate = self } + func removeLocalRenderer(_ renderer: RTCEAGLVideoView) { + if let activeCall { + if let track = activeCall.localVideoTrack { + track.remove(renderer) + } + } else if let notConnectedCall { + if let track = notConnectedCall.localCameraAndTrack?.1 { + track.remove(renderer) + } + } + renderer.delegate = nil + } + func videoView(_ videoView: RTCVideoRenderer, didChangeVideoSize size: CGSize) { guard size.height > 0 else { return } localRendererAspectRatio.wrappedValue = size.width / size.height } + func setupLocalTracks(_ incomingCall: Bool, _ call: Call) { + let pc = call.connection + let transceivers = call.connection.transceivers + let audioTrack = call.localAudioTrack + let videoTrack = call.localVideoTrack + + if incomingCall { + let micCameraInit = RTCRtpTransceiverInit() + // streamIds required for old versions which adds tracks from stream, not from track property + micCameraInit.streamIds = ["micCamera"] + + let screenAudioVideoInit = RTCRtpTransceiverInit() + screenAudioVideoInit.streamIds = ["screenAudioVideo"] + + // incoming call, no transceivers yet. But they should be added in order: mic, camera, screen audio, screen video + // mid = 0, mic + if let audioTrack { + pc.addTransceiver(with: audioTrack, init: micCameraInit) + } else { + pc.addTransceiver(of: .audio, init: micCameraInit) + } + // mid = 1, camera + if let videoTrack { + pc.addTransceiver(with: videoTrack, init: micCameraInit) + } else { + pc.addTransceiver(of: .video, init: micCameraInit) + } + // mid = 2, screenAudio + pc.addTransceiver(of: .audio, init: screenAudioVideoInit) + // mid = 3, screenVideo + pc.addTransceiver(of: .video, init: screenAudioVideoInit) + } else { + // new version + if transceivers.count > 2 { + // Outgoing call. All transceivers are ready. Don't addTrack() because it will create new transceivers, replace existing (nil) tracks + transceivers + .first(where: { elem in mediaSourceFromTransceiverMid(elem.mid) == .mic })? + .sender.track = audioTrack + transceivers + .first(where: { elem in mediaSourceFromTransceiverMid(elem.mid) == .camera })? + .sender.track = videoTrack + } else { + // old version, only two transceivers + if let audioTrack { + pc.add(audioTrack, streamIds: ["micCamera"]) + } else { + // it's important to have any track in order to be able to turn it on again (currently it's off) + let sender = pc.add(createAudioTrack(), streamIds: ["micCamera"]) + sender?.track = nil + } + if let videoTrack { + pc.add(videoTrack, streamIds: ["micCamera"]) + } else { + // it's important to have any track in order to be able to turn it on again (currently it's off) + let localVideoSource = WebRTCClient.factory.videoSource() + let localVideoTrack = WebRTCClient.factory.videoTrack(with: localVideoSource, trackId: "video0") + let sender = pc.add(localVideoTrack, streamIds: ["micCamera"]) + sender?.track = nil + } + } + } + } + + func mediaSourceFromTransceiverMid(_ mid: String?) -> CallMediaSource { + switch mid { + case "0": + return .mic + case "1": + return .camera + case "2": + return .screenAudio + case "3": + return .screenVideo + default: + return .unknown + } + } + + // Should be called after local description set + func setupEncryptionForLocalTracks(_ call: Call) { + if let encryptor = call.frameEncryptor { + call.connection.senders.forEach { $0.setRtcFrameEncryptor(encryptor) } + } + } + func frameDecryptor(_ decryptor: RTCFrameDecryptor, mediaType: RTCRtpMediaType, withFrame encrypted: Data) -> Data? { guard encrypted.count > 0 else { return nil } - if var key: [CChar] = activeCall.wrappedValue?.aesKey?.cString(using: .utf8), + if var key: [CChar] = activeCall?.aesKey?.cString(using: .utf8), let pointer: UnsafeMutableRawPointer = malloc(encrypted.count) { memcpy(pointer, (encrypted as NSData).bytes, encrypted.count) let isKeyFrame = encrypted[0] & 1 == 0 @@ -304,7 +512,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg func frameEncryptor(_ encryptor: RTCFrameEncryptor, mediaType: RTCRtpMediaType, withFrame unencrypted: Data) -> Data? { guard unencrypted.count > 0 else { return nil } - if var key: [CChar] = activeCall.wrappedValue?.aesKey?.cString(using: .utf8), + if var key: [CChar] = activeCall?.aesKey?.cString(using: .utf8), let pointer: UnsafeMutableRawPointer = malloc(unencrypted.count + WebRTCClient.ivTagBytes) { memcpy(pointer, (unencrypted as NSData).bytes, unencrypted.count) let isKeyFrame = unencrypted[0] & 1 == 0 @@ -327,18 +535,42 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg } } - func addRemoteRenderer(_ activeCall: Call, _ renderer: RTCVideoRenderer) { - activeCall.remoteStream?.add(renderer) + func addRemoteCameraRenderer(_ renderer: RTCVideoRenderer) { + if activeCall?.remoteVideoTrack != nil { + activeCall?.remoteVideoTrack?.add(renderer) + } else { + cameraRenderers.append(renderer) + } } - func removeRemoteRenderer(_ activeCall: Call, _ renderer: RTCVideoRenderer) { - activeCall.remoteStream?.remove(renderer) + func removeRemoteCameraRenderer(_ renderer: RTCVideoRenderer) { + if activeCall?.remoteVideoTrack != nil { + activeCall?.remoteVideoTrack?.remove(renderer) + } else { + cameraRenderers.removeAll(where: { $0.isEqual(renderer) }) + } } - func startCaptureLocalVideo(_ activeCall: Call) { + func addRemoteScreenRenderer(_ renderer: RTCVideoRenderer) { + if activeCall?.remoteScreenVideoTrack != nil { + activeCall?.remoteScreenVideoTrack?.add(renderer) + } else { + screenRenderers.append(renderer) + } + } + + func removeRemoteScreenRenderer(_ renderer: RTCVideoRenderer) { + if activeCall?.remoteScreenVideoTrack != nil { + activeCall?.remoteScreenVideoTrack?.remove(renderer) + } else { + screenRenderers.removeAll(where: { $0.isEqual(renderer) }) + } + } + + func startCaptureLocalVideo(_ device: AVCaptureDevice.Position?, _ capturer: RTCVideoCapturer?) { #if targetEnvironment(simulator) guard - let capturer = activeCall.localCamera as? RTCFileVideoCapturer + let capturer = (activeCall?.localCamera ?? notConnectedCall?.localCameraAndTrack?.0) as? RTCFileVideoCapturer else { logger.error("Unable to work with a file capturer") return @@ -348,10 +580,10 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg capturer.startCapturing(fromFileNamed: "sounds/video.mp4") #else guard - let capturer = activeCall.localCamera as? RTCCameraVideoCapturer, - let camera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == activeCall.device }) + let capturer = capturer as? RTCCameraVideoCapturer, + let camera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == device }) else { - logger.error("Unable to find a camera") + logger.error("Unable to find a camera or local track") return } @@ -377,19 +609,6 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg #endif } - private func createAudioSender(_ connection: RTCPeerConnection) { - let streamId = "stream" - let audioTrack = createAudioTrack() - connection.add(audioTrack, streamIds: [streamId]) - } - - private func createVideoSender(_ connection: RTCPeerConnection) -> (RTCVideoTrack?, RTCVideoTrack?, RTCVideoCapturer?, RTCVideoSource?) { - let streamId = "stream" - let (localVideoTrack, localCamera, localVideoSource) = createVideoTrack() - connection.add(localVideoTrack, streamIds: [streamId]) - return (localVideoTrack, connection.transceivers.first { $0.mediaType == .video }?.receiver.track as? RTCVideoTrack, localCamera, localVideoSource) - } - private func createAudioTrack() -> RTCAudioTrack { let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) let audioSource = WebRTCClient.factory.audioSource(with: audioConstrains) @@ -397,7 +616,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg return audioTrack } - private func createVideoTrack() -> (RTCVideoTrack, RTCVideoCapturer, RTCVideoSource) { + private func createVideoTrackAndStartCapture(_ device: AVCaptureDevice.Position) -> (RTCVideoCapturer, RTCVideoTrack) { let localVideoSource = WebRTCClient.factory.videoSource() #if targetEnvironment(simulator) @@ -407,7 +626,8 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg #endif let localVideoTrack = WebRTCClient.factory.videoTrack(with: localVideoSource, trackId: "video0") - return (localVideoTrack, localCamera, localVideoSource) + startCaptureLocalVideo(device, localCamera) + return (localCamera, localVideoTrack) } func endCall() { @@ -420,15 +640,16 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg } private func _endCall() { - guard let call = activeCall.wrappedValue else { return } + (notConnectedCall?.localCameraAndTrack?.0 as? RTCCameraVideoCapturer)?.stopCapture() + guard let call = activeCall else { return } logger.debug("WebRTCClient: ending the call") - activeCall.wrappedValue = nil - (call.localCamera as? RTCCameraVideoCapturer)?.stopCapture() call.connection.close() call.connection.delegate = nil call.frameEncryptor?.delegate = nil call.frameDecryptor?.delegate = nil + (call.localCamera as? RTCCameraVideoCapturer)?.stopCapture() audioSessionToDefaults() + activeCall = nil } func untilIceComplete(timeoutMs: UInt64, stepMs: UInt64, action: @escaping () async -> Void) async { @@ -437,7 +658,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg _ = try? await Task.sleep(nanoseconds: stepMs * 1000000) t += stepMs await action() - } while t < timeoutMs && activeCall.wrappedValue?.connection.iceGatheringState != .complete + } while t < timeoutMs && activeCall?.connection.iceGatheringState != .complete } } @@ -498,11 +719,40 @@ extension WebRTCClient: RTCPeerConnectionDelegate { logger.debug("Connection should negotiate") } + func peerConnection(_ peerConnection: RTCPeerConnection, didStartReceivingOn transceiver: RTCRtpTransceiver) { + if let track = transceiver.receiver.track { + DispatchQueue.main.async { + // Doesn't work for outgoing video call (audio in video call works ok still, same as incoming call) +// if let decryptor = self.activeCall?.frameDecryptor { +// transceiver.receiver.setRtcFrameDecryptor(decryptor) +// } + let source = self.mediaSourceFromTransceiverMid(transceiver.mid) + switch source { + case .mic: self.activeCall?.remoteAudioTrack = track as? RTCAudioTrack + case .camera: + self.activeCall?.remoteVideoTrack = track as? RTCVideoTrack + self.cameraRenderers.forEach({ renderer in + self.activeCall?.remoteVideoTrack?.add(renderer) + }) + self.cameraRenderers.removeAll() + case .screenAudio: self.activeCall?.remoteScreenAudioTrack = track as? RTCAudioTrack + case .screenVideo: + self.activeCall?.remoteScreenVideoTrack = track as? RTCVideoTrack + self.screenRenderers.forEach({ renderer in + self.activeCall?.remoteScreenVideoTrack?.add(renderer) + }) + self.screenRenderers.removeAll() + case .unknown: () + } + } + self.setupMuteUnmuteListener(transceiver, track) + } + } + func peerConnection(_ connection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { debugPrint("Connection new connection state: \(newState.toString() ?? "" + newState.rawValue.description) \(connection.receivers)") - guard let call = activeCall.wrappedValue, - let connectionStateString = newState.toString(), + guard let connectionStateString = newState.toString(), let iceConnectionStateString = connection.iceConnectionState.toString(), let iceGatheringStateString = connection.iceGatheringState.toString(), let signalingStateString = connection.signalingState.toString() @@ -523,18 +773,14 @@ extension WebRTCClient: RTCPeerConnectionDelegate { switch newState { case .checking: - if let frameDecryptor = activeCall.wrappedValue?.frameDecryptor { + if let frameDecryptor = activeCall?.frameDecryptor { connection.receivers.forEach { $0.setRtcFrameDecryptor(frameDecryptor) } } - let enableSpeaker: Bool - switch call.localMedia { - case .video: enableSpeaker = true - default: enableSpeaker = false - } + let enableSpeaker: Bool = ChatModel.shared.activeCall?.localMediaSources.hasVideo == true setSpeakerEnabledAndConfigureSession(enableSpeaker) case .connected: sendConnectedEvent(connection) case .disconnected, .failed: endCall() - default: do {} + default: () } } } @@ -546,7 +792,7 @@ extension WebRTCClient: RTCPeerConnectionDelegate { func peerConnection(_ connection: RTCPeerConnection, didGenerate candidate: WebRTC.RTCIceCandidate) { // logger.debug("Connection generated candidate \(candidate.debugDescription)") Task { - await self.activeCall.wrappedValue?.iceCandidates.append(candidate.toCandidate(nil, nil)) + await self.activeCall?.iceCandidates.append(candidate.toCandidate(nil, nil)) } } @@ -601,11 +847,42 @@ extension WebRTCClient: RTCPeerConnectionDelegate { } extension WebRTCClient { - func setAudioEnabled(_ enabled: Bool) { - setTrackEnabled(RTCAudioTrack.self, enabled) + static func isAuthorized(for type: AVMediaType) async -> Bool { + let status = AVCaptureDevice.authorizationStatus(for: type) + var isAuthorized = status == .authorized + if status == .notDetermined { + isAuthorized = await AVCaptureDevice.requestAccess(for: type) + } + return isAuthorized } - func setSpeakerEnabledAndConfigureSession( _ enabled: Bool) { + static func showUnauthorizedAlert(for type: AVMediaType) { + if type == .audio { + AlertManager.shared.showAlert(Alert( + title: Text("No permission to record speech"), + message: Text("To record speech please grant permission to use Microphone."), + primaryButton: .default(Text("Open Settings")) { + DispatchQueue.main.async { + UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) + } + }, + secondaryButton: .cancel() + )) + } else if type == .video { + AlertManager.shared.showAlert(Alert( + title: Text("No permission to record video"), + message: Text("To record video please grant permission to use Camera."), + primaryButton: .default(Text("Open Settings")) { + DispatchQueue.main.async { + UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) + } + }, + secondaryButton: .cancel() + )) + } + } + + func setSpeakerEnabledAndConfigureSession( _ enabled: Bool, skipExternalDevice: Bool = false) { logger.debug("WebRTCClient: configuring session with speaker enabled \(enabled)") audioQueue.async { [weak self] in guard let self = self else { return } @@ -618,7 +895,7 @@ extension WebRTCClient { if enabled { try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.defaultToSpeaker, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP]) try self.rtcAudioSession.setMode(AVAudioSession.Mode.videoChat.rawValue) - if hasExternalAudioDevice, let preferred = self.rtcAudioSession.session.preferredInputDevice() { + if hasExternalAudioDevice && !skipExternalDevice, let preferred = self.rtcAudioSession.session.preferredInputDevice() { try self.rtcAudioSession.setPreferredInput(preferred) } else { try self.rtcAudioSession.overrideOutputAudioPort(.speaker) @@ -628,7 +905,7 @@ extension WebRTCClient { try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue) try self.rtcAudioSession.overrideOutputAudioPort(.none) } - if hasExternalAudioDevice { + if hasExternalAudioDevice && !skipExternalDevice { logger.debug("WebRTCClient: configuring session with external device available, skip configuring speaker") } try self.rtcAudioSession.setActive(true) @@ -659,25 +936,59 @@ extension WebRTCClient { } } - func setVideoEnabled(_ enabled: Bool) { - setTrackEnabled(RTCVideoTrack.self, enabled) + @MainActor + func setAudioEnabled(_ enabled: Bool) { + if activeCall != nil { + activeCall?.localAudioTrack = enabled ? createAudioTrack() : nil + activeCall?.connection.transceivers.first(where: { t in mediaSourceFromTransceiverMid(t.mid) == .mic })?.sender.track = activeCall?.localAudioTrack + } else if notConnectedCall != nil { + notConnectedCall?.audioTrack = enabled ? createAudioTrack() : nil + } + ChatModel.shared.activeCall?.localMediaSources.mic = enabled + } + + @MainActor + func setCameraEnabled(_ enabled: Bool) { + if let call = activeCall { + if enabled { + if call.localVideoTrack == nil { + let device = activeCall?.device ?? notConnectedCall?.device ?? .front + let (camera, track) = createVideoTrackAndStartCapture(device) + activeCall?.localCamera = camera + activeCall?.localVideoTrack = track + } + } else { + (call.localCamera as? RTCCameraVideoCapturer)?.stopCapture() + activeCall?.localCamera = nil + activeCall?.localVideoTrack = nil + } + call.connection.transceivers + .first(where: { t in mediaSourceFromTransceiverMid(t.mid) == .camera })? + .sender.track = activeCall?.localVideoTrack + ChatModel.shared.activeCall?.localMediaSources.camera = activeCall?.localVideoTrack != nil + } else if let call = notConnectedCall { + if enabled { + let device = activeCall?.device ?? notConnectedCall?.device ?? .front + notConnectedCall?.localCameraAndTrack = createVideoTrackAndStartCapture(device) + } else { + (call.localCameraAndTrack?.0 as? RTCCameraVideoCapturer)?.stopCapture() + notConnectedCall?.localCameraAndTrack = nil + } + ChatModel.shared.activeCall?.localMediaSources.camera = notConnectedCall?.localCameraAndTrack != nil + } } func flipCamera() { - switch activeCall.wrappedValue?.device { - case .front: activeCall.wrappedValue?.device = .back - case .back: activeCall.wrappedValue?.device = .front - default: () + let device = activeCall?.device ?? notConnectedCall?.device + if activeCall != nil { + activeCall?.device = device == .front ? .back : .front + } else { + notConnectedCall?.device = device == .front ? .back : .front } - if let call = activeCall.wrappedValue { - startCaptureLocalVideo(call) - } - } - - private func setTrackEnabled(_ type: T.Type, _ enabled: Bool) { - activeCall.wrappedValue?.connection.transceivers - .compactMap { $0.sender.track as? T } - .forEach { $0.isEnabled = enabled } + startCaptureLocalVideo( + activeCall?.device ?? notConnectedCall?.device, + (activeCall?.localCamera ?? notConnectedCall?.localCameraAndTrack?.0) as? RTCCameraVideoCapturer + ) } } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift index 8c9112a858..62a41c504a 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift @@ -45,7 +45,7 @@ struct ChatInfoToolbar: View { } private var contactVerifiedShield: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .font(.caption) .foregroundColor(theme.colors.secondary) .baselineOffset(1) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index ea3b04c2ff..8194c8fe6f 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -7,7 +7,7 @@ // import SwiftUI -import SimpleXChat +@preconcurrency import SimpleXChat func infoRow(_ title: LocalizedStringKey, _ value: String) -> some View { HStack { @@ -96,6 +96,8 @@ struct ChatInfoView: View { @ObservedObject var chat: Chat @State var contact: Contact @State var localAlias: String + @State var featuresAllowed: ContactFeaturesAllowed + @State var currentFeaturesAllowed: ContactFeaturesAllowed var onSearch: () -> Void @State private var connectionStats: ConnectionStats? = nil @State private var customUserProfile: Profile? = nil @@ -107,6 +109,7 @@ struct ChatInfoView: View { @State private var showConnectContactViaAddressDialog = false @State private var sendReceipts = SendReceipts.userDefault(true) @State private var sendReceiptsUserDefault = true + @State private var progressIndicator = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false enum ChatInfoViewAlert: Identifiable { @@ -135,50 +138,50 @@ struct ChatInfoView: View { var body: some View { NavigationView { - List { - contactInfoHeader() - .listRowBackground(Color.clear) - .contentShape(Rectangle()) - .onTapGesture { - aliasTextFieldFocused = false - } - - Group { + ZStack { + List { + contactInfoHeader() + .listRowBackground(Color.clear) + .contentShape(Rectangle()) + .onTapGesture { + aliasTextFieldFocused = false + } + localAliasTextEdit() - } - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .padding(.bottom, 18) - - GeometryReader { g in - HStack(alignment: .center, spacing: 8) { - let buttonWidth = g.size.width / 4 - searchButton(width: buttonWidth) - AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } - VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } - muteButton(width: buttonWidth) - } - } - .padding(.trailing) - .frame(maxWidth: .infinity) - .frame(height: infoViewActionButtonHeight) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8)) - - if let customUserProfile = customUserProfile { - Section(header: Text("Incognito").foregroundColor(theme.colors.secondary)) { - HStack { - Text("Your random profile") - Spacer() - Text(customUserProfile.chatViewName) - .foregroundStyle(.indigo) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .padding(.bottom, 18) + + GeometryReader { g in + HStack(alignment: .center, spacing: 8) { + let buttonWidth = g.size.width / 4 + searchButton(width: buttonWidth) + AudioCallButton(chat: chat, contact: contact, connectionStats: $connectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } + VideoButton(chat: chat, contact: contact, connectionStats: $connectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } + if let nextNtfMode = chat.chatInfo.nextNtfMode { + muteButton(width: buttonWidth, nextNtfMode: nextNtfMode) + } } } - } - - Section { - Group { + .padding(.trailing) + .frame(maxWidth: .infinity) + .frame(height: infoViewActionButtonHeight) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8)) + + if let customUserProfile = customUserProfile { + Section(header: Text("Incognito").foregroundColor(theme.colors.secondary)) { + HStack { + Text("Your random profile") + Spacer() + Text(customUserProfile.chatViewName) + .foregroundStyle(.indigo) + } + } + } + + Section { if let code = connectionCode { verifyCodeButton(code) } contactPreferencesButton() sendReceiptsOption() @@ -189,97 +192,109 @@ struct ChatInfoView: View { // } else if developerTools { // synchronizeConnectionButtonForce() // } + + NavigationLink { + ChatWallpaperEditorSheet(chat: chat) + } label: { + Label("Chat theme", systemImage: "photo") + } + // } else if developerTools { + // synchronizeConnectionButtonForce() + // } } .disabled(!contact.ready || !contact.active) - NavigationLink { - ChatWallpaperEditorSheet(chat: chat) - } label: { - Label("Chat theme", systemImage: "photo") - } - // } else if developerTools { - // synchronizeConnectionButtonForce() - // } - } - .disabled(!contact.ready || !contact.active) - - if let conn = contact.activeConn { + Section { - infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard") - } - } - - if let contactLink = contact.contactLink { - Section { - SimpleXLinkQRCode(uri: contactLink) - Button { - showShareSheet(items: [simplexChatLink(contactLink)]) - } label: { - Label("Share address", systemImage: "square.and.arrow.up") - } - } header: { - Text("Address") - .foregroundColor(theme.colors.secondary) + ChatTTLOption(chat: chat, progressIndicator: $progressIndicator) } footer: { - Text("You can share this address with your contacts to let them connect with **\(contact.displayName)**.") - .foregroundColor(theme.colors.secondary) + Text("Delete chat messages from your device.") } - } - - if contact.ready && contact.active { - Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { - networkStatusRow() - .onTapGesture { - alert = .networkStatusAlert + + if let conn = contact.activeConn { + Section { + infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard") + } + } + + if let contactLink = contact.contactLink { + Section { + SimpleXLinkQRCode(uri: contactLink) + Button { + showShareSheet(items: [simplexChatLink(contactLink)]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") } - if let connStats = connectionStats { - Button("Change receiving address") { - alert = .switchAddressAlert - } - .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } - || connStats.ratchetSyncSendProhibited - ) - if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { - Button("Abort changing address") { - alert = .abortSwitchAddressAlert + } header: { + Text("Address") + .foregroundColor(theme.colors.secondary) + } footer: { + Text("You can share this address with your contacts to let them connect with **\(contact.displayName)**.") + .foregroundColor(theme.colors.secondary) + } + } + + if contact.ready && contact.active { + Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { + networkStatusRow() + .onTapGesture { + alert = .networkStatusAlert + } + if let connStats = connectionStats { + Button("Change receiving address") { + alert = .switchAddressAlert } .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } || connStats.ratchetSyncSendProhibited ) + if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { + Button("Abort changing address") { + alert = .abortSwitchAddressAlert + } + .disabled( + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + || connStats.ratchetSyncSendProhibited + ) + } + smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) + smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } - smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) - smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } } - } - - Section { - clearChatButton() - deleteContactButton() - } - - if developerTools { - Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { - infoRow("Local name", chat.chatInfo.localDisplayName) - infoRow("Database ID", "\(chat.chatInfo.apiId)") - Button ("Debug delivery") { - Task { - do { - let info = queueInfoText(try await apiContactQueueInfo(chat.chatInfo.apiId)) - await MainActor.run { alert = .queueInfo(info: info) } - } catch let e { - logger.error("apiContactQueueInfo error: \(responseError(e))") - let a = getErrorAlert(e, "Error") - await MainActor.run { alert = .error(title: a.title, error: a.message) } + + Section { + clearChatButton() + deleteContactButton() + } + + if developerTools { + Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { + infoRow("Local name", chat.chatInfo.localDisplayName) + infoRow("Database ID", "\(chat.chatInfo.apiId)") + Button ("Debug delivery") { + Task { + do { + let info = queueInfoText(try await apiContactQueueInfo(chat.chatInfo.apiId)) + await MainActor.run { alert = .queueInfo(info: info) } + } catch let e { + logger.error("apiContactQueueInfo error: \(responseError(e))") + let a = getErrorAlert(e, "Error") + await MainActor.run { alert = .error(title: a.title, error: a.message) } + } } } } } } + .modifier(ThemedBackground(grouped: true)) + .navigationBarHidden(true) + .disabled(progressIndicator) + .opacity(progressIndicator ? 0.6 : 1) + + if progressIndicator { + ProgressView().scaleEffect(2) + } } - .modifier(ThemedBackground(grouped: true)) - .navigationBarHidden(true) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .onAppear { @@ -288,7 +303,6 @@ struct ChatInfoView: View { } sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault) - Task { do { let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId) @@ -312,7 +326,15 @@ struct ChatInfoView: View { case .networkStatusAlert: return networkStatusAlert() case .switchAddressAlert: return switchAddressAlert(switchContactAddress) case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress) - case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) }) + case .syncConnectionForceAlert: + return syncConnectionForceAlert({ + Task { + if let stats = await syncContactConnection(contact, force: true, showAlert: { alert = .someAlert(alert: $0) }) { + connectionStats = stats + dismiss() + } + } + }) case let .queueInfo(info): return queueInfoAlert(info) case let .someAlert(a): return a.alert case let .error(title, error): return mkAlert(title: title, message: error) @@ -322,11 +344,21 @@ struct ChatInfoView: View { .sheet(item: $sheet) { if #available(iOS 16.0, *) { $0.content - .presentationDetents([.fraction(0.4)]) + .presentationDetents([.fraction($0.fraction)]) } else { $0.content } } + .onDisappear { + if currentFeaturesAllowed != featuresAllowed { + showAlert( + title: NSLocalizedString("Save preferences?", comment: "alert title"), + buttonTitle: NSLocalizedString("Save and notify contact", comment: "alert button"), + buttonAction: { savePreferences() }, + cancelButton: true + ) + } + } } private func contactInfoHeader() -> some View { @@ -339,7 +371,7 @@ struct ChatInfoView: View { Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) - + Text(" ") + + textSpace + Text(contact.profile.displayName) .font(.largeTitle) ) @@ -402,13 +434,13 @@ struct ChatInfoView: View { .disabled(!contact.ready || chat.chatItems.isEmpty) } - private func muteButton(width: CGFloat) -> some View { - InfoViewButton( - image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill", - title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute", + private func muteButton(width: CGFloat, nextNtfMode: MsgFilter) -> some View { + return InfoViewButton( + image: nextNtfMode.iconFilled, + title: "\(nextNtfMode.text(mentions: false))", width: width ) { - toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) + toggleNotifications(chat, enableNtfs: nextNtfMode) } .disabled(!contact.ready || !contact.active) } @@ -447,8 +479,9 @@ struct ChatInfoView: View { NavigationLink { ContactPreferencesView( contact: $contact, - featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), - currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences) + featuresAllowed: $featuresAllowed, + currentFeaturesAllowed: $currentFeaturesAllowed, + savePreferences: savePreferences ) .navigationBarTitle("Contact preferences") .modifier(ThemedBackground(grouped: true)) @@ -477,10 +510,15 @@ struct ChatInfoView: View { chatSettings.sendRcpts = sendReceipts.bool() updateChatSettings(chat, chatSettings: chatSettings) } - + private func synchronizeConnectionButton() -> some View { Button { - syncContactConnection(force: false) + Task { + if let stats = await syncContactConnection(contact, force: false, showAlert: { alert = .someAlert(alert: $0) }) { + connectionStats = stats + dismiss() + } + } } label: { Label("Fix connection", systemImage: "exclamationmark.arrow.triangle.2.circlepath") .foregroundColor(.orange) @@ -599,29 +637,113 @@ struct ChatInfoView: View { } } - private func syncContactConnection(force: Bool) { + private func savePreferences() { Task { do { - let stats = try apiSyncContactRatchet(contact.apiId, force) - connectionStats = stats - await MainActor.run { - chatModel.updateContactConnectionStats(contact, stats) - dismiss() + let prefs = contactFeaturesAllowedToPrefs(featuresAllowed) + if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) { + await MainActor.run { + contact = toContact + chatModel.updateContact(toContact) + currentFeaturesAllowed = featuresAllowed + } } - } catch let error { - logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))") - let a = getErrorAlert(error, "Error synchronizing connection") - await MainActor.run { - alert = .error(title: a.title, error: a.message) + } catch { + logger.error("ContactPreferencesView apiSetContactPrefs error: \(responseError(error))") + } + } + } +} + +struct ChatTTLOption: View { + @ObservedObject var chat: Chat + @Binding var progressIndicator: Bool + @State private var currentChatItemTTL: ChatTTL = ChatTTL.userDefault(.seconds(0)) + @State private var chatItemTTL: ChatTTL = ChatTTL.chat(.seconds(0)) + + var body: some View { + Picker("Delete messages after", selection: $chatItemTTL) { + ForEach(ChatItemTTL.values) { ttl in + Text(ttl.deleteAfterText).tag(ChatTTL.chat(ttl)) + } + let defaultTTL = ChatTTL.userDefault(ChatModel.shared.chatItemTTL) + Text(defaultTTL.text).tag(defaultTTL) + + if case .chat(let ttl) = chatItemTTL, case .seconds = ttl { + Text(ttl.deleteAfterText).tag(chatItemTTL) + } + } + .disabled(progressIndicator) + .frame(height: 36) + .onChange(of: chatItemTTL) { ttl in + if ttl == currentChatItemTTL { return } + setChatTTL( + ttl, + hasPreviousTTL: !currentChatItemTTL.neverExpires, + onCancel: { chatItemTTL = currentChatItemTTL } + ) { + progressIndicator = true + Task { + let m = ChatModel.shared + do { + try await setChatTTL(chatType: chat.chatInfo.chatType, id: chat.chatInfo.apiId, ttl) + await loadChat(chat: chat, clearItems: true) + await MainActor.run { + progressIndicator = false + currentChatItemTTL = chatItemTTL + if ItemsModel.shared.reversedChatItems.isEmpty && m.chatId == chat.id, + let chat = m.getChat(chat.id) { + chat.chatItems = [] + m.replaceChat(chat.id, chat) + } + } + } + catch let error { + logger.error("setChatTTL error \(responseError(error))") + await loadChat(chat: chat, clearItems: true) + await MainActor.run { + chatItemTTL = currentChatItemTTL + progressIndicator = false + } + } } } } + .onAppear { + let sm = ChatModel.shared + let ttl = chat.chatInfo.ttl(sm.chatItemTTL) + chatItemTTL = ttl + currentChatItemTTL = ttl + } + } +} + +func syncContactConnection(_ contact: Contact, force: Bool, showAlert: (SomeAlert) -> Void) async -> ConnectionStats? { + do { + let stats = try apiSyncContactRatchet(contact.apiId, force) + await MainActor.run { + ChatModel.shared.updateContactConnectionStats(contact, stats) + } + return stats + } catch let error { + logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))") + let a = getErrorAlert(error, "Error synchronizing connection") + await MainActor.run { + showAlert( + SomeAlert( + alert: mkAlert(title: a.title, message: a.message), + id: "syncContactConnection error" + ) + ) + } + return nil } } struct AudioCallButton: View { var chat: Chat var contact: Contact + @Binding var connectionStats: ConnectionStats? var width: CGFloat var showAlert: (SomeAlert) -> Void @@ -629,6 +751,7 @@ struct AudioCallButton: View { CallButton( chat: chat, contact: contact, + connectionStats: $connectionStats, image: "phone.fill", title: "call", mediaType: .audio, @@ -641,6 +764,7 @@ struct AudioCallButton: View { struct VideoButton: View { var chat: Chat var contact: Contact + @Binding var connectionStats: ConnectionStats? var width: CGFloat var showAlert: (SomeAlert) -> Void @@ -648,6 +772,7 @@ struct VideoButton: View { CallButton( chat: chat, contact: contact, + connectionStats: $connectionStats, image: "video.fill", title: "video", mediaType: .video, @@ -660,6 +785,7 @@ struct VideoButton: View { private struct CallButton: View { var chat: Chat var contact: Contact + @Binding var connectionStats: ConnectionStats? var image: String var title: LocalizedStringKey var mediaType: CallMediaType @@ -671,12 +797,40 @@ private struct CallButton: View { InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) { if canCall { - if CallController.useCallKit() { - CallController.shared.startCall(contact, mediaType) - } else { - // When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first - dismissAllSheets(animated: true) { - CallController.shared.startCall(contact, mediaType) + if let connStats = connectionStats { + if connStats.ratchetSyncState == .ok { + if CallController.useCallKit() { + CallController.shared.startCall(contact, mediaType) + } else { + // When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first + dismissAllSheets(animated: true) { + CallController.shared.startCall(contact, mediaType) + } + } + } else if connStats.ratchetSyncAllowed { + showAlert(SomeAlert( + alert: Alert( + title: Text("Fix connection?"), + message: Text("Connection requires encryption renegotiation."), + primaryButton: .default(Text("Fix")) { + Task { + if let stats = await syncContactConnection(contact, force: false, showAlert: showAlert) { + connectionStats = stats + } + } + }, + secondaryButton: .cancel() + ), + id: "can't call contact, fix connection" + )) + } else { + showAlert(SomeAlert( + alert: mkAlert( + title: "Can't call contact", + message: "Encryption renegotiation in progress." + ), + id: "can't call contact, encryption renegotiation in progress" + )) } } } else if contact.nextSendGrpInv { @@ -942,7 +1096,7 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al ) } -func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String { +func queueInfoText(_ info: (RcvMsgInfo?, ServerQueueInfo)) -> String { let (rcvMsgInfo, qInfo) = info var msgInfo: String if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" } @@ -975,6 +1129,33 @@ func deleteContactDialog( } } +func setChatTTL(_ ttl: ChatTTL, hasPreviousTTL: Bool, onCancel: @escaping () -> Void, onConfirm: @escaping () -> Void) { + let title = if ttl.neverExpires { + NSLocalizedString("Disable automatic message deletion?", comment: "alert title") + } else if ttl.usingDefault || hasPreviousTTL { + NSLocalizedString("Change automatic message deletion?", comment: "alert title") + } else { + NSLocalizedString("Enable automatic message deletion?", comment: "alert title") + } + + let message = if ttl.neverExpires { + NSLocalizedString("Messages in this chat will never be deleted.", comment: "alert message") + } else { + NSLocalizedString("This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted.", comment: "alert message") + } + + showAlert(title, message: message) { + [ + UIAlertAction( + title: ttl.neverExpires ? NSLocalizedString("Disable delete messages", comment: "alert button") : NSLocalizedString("Delete messages", comment: "alert button"), + style: .destructive, + handler: { _ in onConfirm() } + ), + UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel, handler: { _ in onCancel() }) + ] + } +} + private func deleteContactOrConversationDialog( _ chat: Chat, _ contact: Contact, @@ -1173,7 +1354,9 @@ struct ChatInfoView_Previews: PreviewProvider { chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), contact: Contact.sampleData, localAlias: "", - onSearch: {} + featuresAllowed: contactUserPrefsToFeaturesAllowed(Contact.sampleData.mergedPreferences), + currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(Contact.sampleData.mergedPreferences), + onSearch: {} ) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift index 3b3e1b3899..0283e9c07e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift @@ -50,7 +50,7 @@ struct CICallItemView: View { Image(systemName: "phone.connection").foregroundColor(.green) } - @ViewBuilder private func endedCallIcon(_ sent: Bool) -> some View { + private func endedCallIcon(_ sent: Bool) -> some View { HStack { Image(systemName: "phone.down") Text(durationText(duration)).foregroundColor(theme.colors.secondary) @@ -60,16 +60,16 @@ struct CICallItemView: View { @ViewBuilder private func acceptCallButton() -> some View { if case let .direct(contact) = chat.chatInfo { - Button { - if let invitation = m.callInvitations[contact.id] { - CallController.shared.answerCall(invitation: invitation) - logger.debug("acceptCallButton call answered") - } else { - AlertManager.shared.showAlertMsg(title: "Call already ended!") - } - } label: { - Label("Answer call", systemImage: "phone.arrow.down.left") - } + Label("Answer call", systemImage: "phone.arrow.down.left") + .foregroundColor(theme.colors.primary) + .simultaneousGesture(TapGesture().onEnded { + if let invitation = m.callInvitations[contact.id] { + CallController.shared.answerCall(invitation: invitation) + logger.debug("acceptCallButton call answered") + } else { + AlertManager.shared.showAlertMsg(title: "Call already ended!") + } + }) } else { Image(systemName: "phone.arrow.down.left").foregroundColor(theme.colors.secondary) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift index 27d8d9c2de..02be8af73b 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift @@ -11,11 +11,11 @@ import SimpleXChat struct CIChatFeatureView: View { @EnvironmentObject var m: ChatModel + @Environment(\.revealed) var revealed: Bool @ObservedObject var im = ItemsModel.shared @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme var chatItem: ChatItem - @Binding var revealed: Bool var feature: Feature var icon: String? = nil var iconColor: Color @@ -106,6 +106,9 @@ struct CIChatFeatureView: View { struct CIChatFeatureView_Previews: PreviewProvider { static var previews: some View { let enabled = FeatureEnabled(forUser: false, forContact: false) - CIChatFeatureView(chat: Chat.sampleData, chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary)) + CIChatFeatureView( + chat: Chat.sampleData, + chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary) + ).environment(\.revealed, true) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift index 752f599c8d..67f7b69e2c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift @@ -26,9 +26,9 @@ struct CIFeaturePreferenceView: View { allowed != .no && ct.allowsFeature(feature) && !ct.userAllowsFeature(feature) { let setParam = feature == .timedMessages && ct.mergedPreferences.timedMessages.userPreference.preference.ttl == nil featurePreferenceView(acceptText: setParam ? "Set 1 day" : "Accept") - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { allowFeatureToContact(ct, feature, param: setParam ? 86400 : nil) - } + }) } else { featurePreferenceView() } @@ -47,7 +47,7 @@ struct CIFeaturePreferenceView: View { + Text(acceptText) .fontWeight(.medium) .foregroundColor(theme.colors.primary) - + Text(" ") + + Text(verbatim: " ") } r = r + chatItem.timestampText .fontWeight(.light) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index fcb330c321..b0b404d8b5 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -19,42 +19,42 @@ struct CIFileView: View { var body: some View { if smallViewSize != nil { fileIndicator() - .onTapGesture(perform: fileAction) + .simultaneousGesture(TapGesture().onEnded(fileAction)) } else { let metaReserve = edited ? " " : " " - Button(action: fileAction) { - HStack(alignment: .bottom, spacing: 6) { - fileIndicator() - .padding(.top, 5) - .padding(.bottom, 3) - if let file = file { - let prettyFileSize = ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) - VStack(alignment: .leading, spacing: 2) { - Text(file.fileName) - .lineLimit(1) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.onBackground) - Text(prettyFileSize + metaReserve) - .font(.caption) - .lineLimit(1) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.secondary) - } - } else { - Text(metaReserve) + HStack(alignment: .bottom, spacing: 6) { + fileIndicator() + .padding(.top, 5) + .padding(.bottom, 3) + if let file = file { + let prettyFileSize = ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) + VStack(alignment: .leading, spacing: 2) { + Text(file.fileName) + .lineLimit(1) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.onBackground) + Text(prettyFileSize + metaReserve) + .font(.caption) + .lineLimit(1) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.secondary) } + } else { + Text(metaReserve) } - .padding(.top, 4) - .padding(.bottom, 6) - .padding(.leading, 10) - .padding(.trailing, 12) } + .padding(.top, 4) + .padding(.bottom, 6) + .padding(.leading, 10) + .padding(.trailing, 12) + .simultaneousGesture(TapGesture().onEnded(fileAction)) .disabled(!itemInteractive) } } + @inline(__always) private var itemInteractive: Bool { if let file = file { switch (file.fileStatus) { @@ -118,16 +118,10 @@ struct CIFileView: View { } case let .rcvError(rcvFileError): logger.debug("CIFileView fileAction - in .rcvError") - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(rcvFileError.errorInfo) - )) + showFileErrorAlert(rcvFileError) case let .rcvWarning(rcvFileError): logger.debug("CIFileView fileAction - in .rcvWarning") - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(rcvFileError.errorInfo) - )) + showFileErrorAlert(rcvFileError, temporary: true) case .sndStored: logger.debug("CIFileView fileAction - in .sndStored") if file.fileProtocol == .local, let fileSource = getLoadedFileSource(file) { @@ -140,16 +134,10 @@ struct CIFileView: View { } case let .sndError(sndFileError): logger.debug("CIFileView fileAction - in .sndError") - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(sndFileError.errorInfo) - )) + showFileErrorAlert(sndFileError) case let .sndWarning(sndFileError): logger.debug("CIFileView fileAction - in .sndWarning") - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(sndFileError.errorInfo) - )) + showFileErrorAlert(sndFileError, temporary: true) default: break } } @@ -268,6 +256,26 @@ func saveCryptoFile(_ fileSource: CryptoFile) { } } +func showFileErrorAlert(_ err: FileError, temporary: Bool = false) { + let title: String = if temporary { + NSLocalizedString("Temporary file error", comment: "file error alert title") + } else { + NSLocalizedString("File error", comment: "file error alert title") + } + if let btn = err.moreInfoButton { + showAlert(title, message: err.errorInfo) { + [ + okAlertAction, + UIAlertAction(title: NSLocalizedString("How it works", comment: "alert button"), style: .default, handler: { _ in + UIApplication.shared.open(contentModerationPostLink) + }) + ] + } + } else { + showAlert(title, message: err.errorInfo) + } +} + struct CIFileView_Previews: PreviewProvider { static var previews: some View { let sentFile: ChatItem = ChatItem( @@ -285,17 +293,18 @@ struct CIFileView_Previews: PreviewProvider { file: nil ) Group { - ChatItemView(chat: Chat.sampleData, chatItem: sentFile, revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, revealed: Binding.constant(false)) + ChatItemView(chat: Chat.sampleData, chatItem: sentFile, scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, scrollToItemId: { _ in }) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 360)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index ef0fec5dfe..3fcf578875 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CIGroupInvitationView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool @ObservedObject var chat: Chat var chatItem: ChatItem var groupInvitation: CIGroupInvitation @@ -44,16 +45,16 @@ struct CIGroupInvitationView: View { Text(chatIncognito ? "Tap to join incognito" : "Tap to join") .foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary) .font(.callout) - + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy) + + Text(verbatim: " ") + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } } else { ( groupInvitationText() - + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy) + + Text(verbatim: " ") + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } @@ -69,7 +70,7 @@ struct CIGroupInvitationView: View { } .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(chatItem, theme)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } .onChange(of: inProgress) { inProgress in @@ -83,12 +84,12 @@ struct CIGroupInvitationView: View { } if action { - v.onTapGesture { + v.simultaneousGesture(TapGesture().onEnded { inProgress = true joinGroup(groupInvitation.groupId) { await MainActor.run { inProgress = false } } - } + }) .disabled(inProgress) } else { v diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index 3966d7e258..d30369339d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CIImageView: View { @EnvironmentObject var m: ChatModel let chatItem: ChatItem + var scrollToItemId: ((ChatItem.ID) -> Void)? = nil var preview: UIImage? let maxWidth: CGFloat var imgWidth: CGFloat? @@ -25,12 +26,14 @@ struct CIImageView: View { if let uiImage = getLoadedImage(file) { Group { if smallView { smallViewImageView(uiImage) } else { imageView(uiImage) } } .fullScreenCover(isPresented: $showFullScreenImage) { - FullScreenMediaView(chatItem: chatItem, image: uiImage, showView: $showFullScreenImage) + FullScreenMediaView(chatItem: chatItem, scrollToItemId: scrollToItemId, image: uiImage, showView: $showFullScreenImage) } .if(!smallView) { view in view.modifier(PrivacyBlur(blurred: $blurred)) } - .onTapGesture { showFullScreenImage = true } + .if(!blurred) { v in + v.simultaneousGesture(TapGesture().onEnded { showFullScreenImage = true }) + } .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenImage = false } @@ -42,7 +45,7 @@ struct CIImageView: View { imageView(preview).modifier(PrivacyBlur(blurred: $blurred)) } } - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { if let file = file { switch file.fileStatus { case .rcvInvitation, .rcvAborted: @@ -69,29 +72,17 @@ struct CIImageView: View { case .rcvComplete: () // ? case .rcvCancelled: () // TODO case let .rcvError(rcvFileError): - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(rcvFileError.errorInfo) - )) + showFileErrorAlert(rcvFileError) case let .rcvWarning(rcvFileError): - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(rcvFileError.errorInfo) - )) + showFileErrorAlert(rcvFileError, temporary: true) case let .sndError(sndFileError): - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(sndFileError.errorInfo) - )) + showFileErrorAlert(sndFileError) case let .sndWarning(sndFileError): - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(sndFileError.errorInfo) - )) + showFileErrorAlert(sndFileError, temporary: true) default: () } } - } + }) } } .onDisappear { @@ -165,9 +156,9 @@ struct CIImageView: View { private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View { Image(systemName: icon) .resizable() + .invertedForegroundStyle() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(.white) .padding(padding) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift index 18fd682646..5e9fa691de 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift @@ -7,10 +7,11 @@ // import SwiftUI +import SimpleXChat struct CIInvalidJSONView: View { @EnvironmentObject var theme: AppTheme - var json: String + var json: Data? @State private var showJSON = false var body: some View { @@ -23,16 +24,16 @@ struct CIInvalidJSONView: View { .padding(.vertical, 6) .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) - .onTapGesture { showJSON = true } + .simultaneousGesture(TapGesture().onEnded { showJSON = true }) .appSheet(isPresented: $showJSON) { - invalidJSONView(json) + invalidJSONView(dataToString(json)) } } } func invalidJSONView(_ json: String) -> some View { VStack(alignment: .leading, spacing: 16) { - Button { + Button { // this is used in the sheet, Button works here showShareSheet(items: [json]) } label: { Image(systemName: "square.and.arrow.up") @@ -49,6 +50,6 @@ func invalidJSONView(_ json: String) -> some View { struct CIInvalidJSONView_Previews: PreviewProvider { static var previews: some View { - CIInvalidJSONView(json: "{}") + CIInvalidJSONView(json: "{}".data(using: .utf8)!) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 3c864ab172..f9dbaede63 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -16,20 +16,20 @@ struct CILinkView: View { var body: some View { VStack(alignment: .center, spacing: 6) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .scaledToFit() .modifier(PrivacyBlur(blurred: $blurred)) + .if(!blurred) { v in + v.simultaneousGesture(TapGesture().onEnded { + openBrowserAlert(uri: linkPreview.uri) + }) + } } VStack(alignment: .leading, spacing: 6) { Text(linkPreview.title) .lineLimit(3) -// if linkPreview.description != "" { -// Text(linkPreview.description) -// .font(.subheadline) -// .lineLimit(12) -// } Text(linkPreview.uri.absoluteString) .font(.caption) .lineLimit(1) @@ -37,10 +37,32 @@ struct CILinkView: View { } .padding(.horizontal, 12) .frame(maxWidth: .infinity, alignment: .leading) + .simultaneousGesture(TapGesture().onEnded { + openBrowserAlert(uri: linkPreview.uri) + }) } } } +func openBrowserAlert(uri: URL) { + showAlert( + NSLocalizedString("Open link?", comment: "alert title"), + message: uri.absoluteString, + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default, + handler: { _ in } + ), + UIAlertAction( + title: NSLocalizedString("Open", comment: "alert action"), + style: .default, + handler: { _ in UIApplication.shared.open(uri) } + ) + ]} + ) +} + struct LargeLinkPreview_Previews: PreviewProvider { static var previews: some View { let preview = LinkPreview( diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift index 463695ddb7..2898a318a9 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift @@ -20,12 +20,11 @@ struct CIMemberCreatedContactView: View { case let .groupRcv(groupMember): if let contactId = groupMember.memberContactId { memberCreatedContactView(openText: "Open") - .onTapGesture { - dismissAllSheets(animated: true) - DispatchQueue.main.async { - m.chatId = "@\(contactId)" + .simultaneousGesture(TapGesture().onEnded { + ItemsModel.shared.loadOpenChat("@\(contactId)") { + dismissAllSheets(animated: true) } - } + }) } else { memberCreatedContactView() } @@ -45,7 +44,7 @@ struct CIMemberCreatedContactView: View { + Text(openText) .fontWeight(.medium) .foregroundColor(theme.colors.primary) - + Text(" ") + + Text(verbatim: " ") } r = r + chatItem.timestampText .fontWeight(.light) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 66b810cf2f..fc73778239 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -12,11 +12,13 @@ import SimpleXChat struct CIMetaView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool var chatItem: ChatItem var metaColor: Color - var paleMetaColor = Color(UIColor.tertiaryLabel) + var paleMetaColor = Color(uiColor: .tertiaryLabel) var showStatus = true var showEdited = true + var invertedMaterial = false @AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false @@ -24,93 +26,145 @@ struct CIMetaView: View { if chatItem.isDeletedContent { chatItem.timestampText.font(.caption).foregroundColor(metaColor) } else { - let meta = chatItem.meta - let ttl = chat.chatInfo.timedMessagesTTL - let encrypted = chatItem.encryptedFile - switch meta.itemStatus { - case let .sndSent(sndProgress): - switch sndProgress { - case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ZStack { + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + color: metaColor, + paleColor: paleMetaColor, + colorMode: invertedMaterial + ? .invertedMaterial + : .normal, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ).invertedForegroundStyle(enabled: invertedMaterial) + if invertedMaterial { + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + colorMode: .normal, + onlyOverrides: true, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ) } - case let .sndRcvd(_, sndProgress): - switch sndProgress { - case .complete: - ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - } - case .partial: - ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - } - } - default: - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) } } } } -enum SentCheckmark { - case sent - case rcvd1 - case rcvd2 +enum MetaColorMode { + // Renders provided colours + case normal + // Fully transparent meta - used for reserving space + case transparent + // Renders white on dark backgrounds and black on light ones + case invertedMaterial + + func resolve(_ c: Color?) -> Color? { + switch self { + case .normal: c + case .transparent: .clear + case .invertedMaterial: nil + } + } + + func statusSpacer(_ sent: Bool) -> Text { + switch self { + case .normal, .transparent: + Text( + sent + ? Image("checkmark.wide") + : Image(systemName: "circlebadge.fill") + ).foregroundColor(.clear) + case .invertedMaterial: textSpace.kerning(13) + } + } } func ciMetaText( _ meta: CIMeta, chatTTL: Int?, encrypted: Bool?, - color: Color = .clear, + color: Color = .clear, // we use this function to reserve space without rendering meta + paleColor: Color? = nil, primaryColor: Color = .accentColor, - transparent: Bool = false, - sent: SentCheckmark? = nil, + colorMode: MetaColorMode = .normal, + onlyOverrides: Bool = false, // only render colors that differ from base showStatus: Bool = true, showEdited: Bool = true, - showViaProxy: Bool + showViaProxy: Bool, + showTimesamp: Bool ) -> Text { var r = Text("") + var space: Text? = nil + let appendSpace = { + if let sp = space { + r = r + sp + space = nil + } + } + let resolved = colorMode.resolve(color) if showEdited, meta.itemEdited { - r = r + statusIconText("pencil", color) + r = r + statusIconText("pencil", resolved) } if meta.disappearing { - r = r + statusIconText("timer", color).font(.caption2) + r = r + statusIconText("timer", resolved).font(.caption2) let ttl = meta.itemTimed?.ttl if ttl != chatTTL { - r = r + Text(shortTimeText(ttl)).foregroundColor(color) + r = r + colored(Text(shortTimeText(ttl)), resolved) } - r = r + Text(" ") + space = textSpace } if showViaProxy, meta.sentViaProxy == true { - r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2) + appendSpace() + r = r + statusIconText("arrow.forward", resolved?.opacity(0.67)).font(.caption2) } if showStatus { - if let (icon, statusColor) = meta.statusIcon(color, primaryColor) { - let t = Text(Image(systemName: icon)).font(.caption2) - let gap = Text(" ").kerning(-1.25) - let t1 = t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) - switch sent { - case nil: r = r + t1 - case .sent: r = r + t1 + gap - case .rcvd1: r = r + t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) + gap - case .rcvd2: r = r + gap + t1 + appendSpace() + if let (image, statusColor) = meta.itemStatus.statusIcon(color, paleColor ?? color, primaryColor) { + let metaColor = if onlyOverrides && statusColor == color { + Color.clear + } else { + colorMode.resolve(statusColor) } - r = r + Text(" ") + r = r + colored(Text(image), metaColor) } else if !meta.disappearing { - r = r + statusIconText("circlebadge.fill", .clear) + Text(" ") + r = r + colorMode.statusSpacer(meta.itemStatus.sent) } + space = textSpace } if let enc = encrypted { - r = r + statusIconText(enc ? "lock" : "lock.open", color) + Text(" ") + appendSpace() + r = r + statusIconText(enc ? "lock" : "lock.open", resolved) + space = textSpace + } + if showTimesamp { + appendSpace() + r = r + colored(meta.timestampText, resolved) } - r = r + meta.timestampText.foregroundColor(color) return r.font(.caption) } -private func statusIconText(_ icon: String, _ color: Color) -> Text { - Text(Image(systemName: icon)).foregroundColor(color) +@inline(__always) +private func statusIconText(_ icon: String, _ color: Color?) -> Text { + colored(Text(Image(systemName: icon)), color) +} + +// Applying `foregroundColor(nil)` breaks `.invertedForegroundStyle` modifier +@inline(__always) +private func colored(_ t: Text, _ color: Color?) -> Text { + if let color { + t.foregroundColor(color) + } else { + t + } } struct CIMetaView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 1f2e16448d..4e5713c263 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -15,6 +15,7 @@ struct CIRcvDecryptionError: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat + @Environment(\.showTimestamp) var showTimestamp: Bool var msgDecryptError: MsgDecryptError var msgCount: UInt32 var chatItem: ChatItem @@ -47,7 +48,7 @@ struct CIRcvDecryptionError: View { if case let .group(groupInfo) = chat.chatInfo, case let .groupRcv(groupMember) = chatItem.chatDir { do { - let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId) + let (member, stats) = try apiGroupMemberInfoSync(groupInfo.apiId, groupMember.groupMemberId) if let s = stats { m.updateGroupMemberConnectionStats(groupInfo, member, s) } @@ -67,38 +68,41 @@ struct CIRcvDecryptionError: View { } } - @ViewBuilder private func viewBody() -> some View { - if case let .direct(contact) = chat.chatInfo, - let contactStats = contact.activeConn?.connectionStats { - if contactStats.ratchetSyncAllowed { - decryptionErrorItemFixButton(syncSupported: true) { - alert = .syncAllowedAlert { syncContactConnection(contact) } + private func viewBody() -> some View { + Group { + if case let .direct(contact) = chat.chatInfo, + let contactStats = contact.activeConn?.connectionStats { + if contactStats.ratchetSyncAllowed { + decryptionErrorItemFixButton(syncSupported: true) { + alert = .syncAllowedAlert { syncContactConnection(contact) } + } + } else if !contactStats.ratchetSyncSupported { + decryptionErrorItemFixButton(syncSupported: false) { + alert = .syncNotSupportedContactAlert + } + } else { + basicDecryptionErrorItem() } - } else if !contactStats.ratchetSyncSupported { - decryptionErrorItemFixButton(syncSupported: false) { - alert = .syncNotSupportedContactAlert + } else if case let .group(groupInfo) = chat.chatInfo, + case let .groupRcv(groupMember) = chatItem.chatDir, + let mem = m.getGroupMember(groupMember.groupMemberId), + let memberStats = mem.wrapped.activeConn?.connectionStats { + if memberStats.ratchetSyncAllowed { + decryptionErrorItemFixButton(syncSupported: true) { + alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) } + } + } else if !memberStats.ratchetSyncSupported { + decryptionErrorItemFixButton(syncSupported: false) { + alert = .syncNotSupportedMemberAlert + } + } else { + basicDecryptionErrorItem() } } else { basicDecryptionErrorItem() } - } else if case let .group(groupInfo) = chat.chatInfo, - case let .groupRcv(groupMember) = chatItem.chatDir, - let mem = m.getGroupMember(groupMember.groupMemberId), - let memberStats = mem.wrapped.activeConn?.connectionStats { - if memberStats.ratchetSyncAllowed { - decryptionErrorItemFixButton(syncSupported: true) { - alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) } - } - } else if !memberStats.ratchetSyncSupported { - decryptionErrorItemFixButton(syncSupported: false) { - alert = .syncNotSupportedMemberAlert - } - } else { - basicDecryptionErrorItem() - } - } else { - basicDecryptionErrorItem() } + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } } private func basicDecryptionErrorItem() -> some View { @@ -117,21 +121,20 @@ struct CIRcvDecryptionError: View { Text(Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")) .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) - + Text(" ") + + textSpace + Text("Fix connection") .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) - + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + + Text(verbatim: " ") + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) } .padding(.horizontal, 12) CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) .padding(.horizontal, 12) } - .onTapGesture(perform: { onClick() }) + .simultaneousGesture(TapGesture().onEnded(onClick)) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) } @@ -141,16 +144,15 @@ struct CIRcvDecryptionError: View { Text(chatItem.content.text) .foregroundColor(.red) .italic() - + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + + Text(verbatim: " ") + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } .padding(.horizontal, 12) CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) .padding(.horizontal, 12) } - .onTapGesture(perform: { onClick() }) + .simultaneousGesture(TapGesture().onEnded(onClick)) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) } @@ -159,13 +161,13 @@ struct CIRcvDecryptionError: View { let why = Text(decryptErrorReason) switch msgDecryptError { case .ratchetHeader: - message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why + message = Text("\(msgCount) messages failed to decrypt.") + textNewLine + why case .tooManySkipped: - message = Text("\(msgCount) messages skipped.") + Text("\n") + why + message = Text("\(msgCount) messages skipped.") + textNewLine + why case .ratchetEarlier: - message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why + message = Text("\(msgCount) messages failed to decrypt.") + textNewLine + why case .other: - message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why + message = Text("\(msgCount) messages failed to decrypt.") + textNewLine + why case .ratchetSync: message = Text("Encryption re-negotiation failed.") } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 4670fc685f..eacbe9360a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -47,57 +47,57 @@ struct CIVideoView: View { let file = chatItem.file ZStack(alignment: smallView ? .topLeading : .center) { ZStack(alignment: .topLeading) { - if let file = file, let preview = preview, let decrypted = urlDecrypted, smallView { - smallVideoView(decrypted, file, preview) - } else if let file = file, let preview = preview, let player = player, let decrypted = urlDecrypted { - videoView(player, decrypted, file, preview, duration) - } else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil, smallView { - smallVideoViewEncrypted(file, defaultPreview) - } else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil { - videoViewEncrypted(file, defaultPreview, duration) - } else if let preview, let file { - Group { if smallView { smallViewImageView(preview, file) } else { imageView(preview) } } - .onTapGesture { - switch file.fileStatus { - case .rcvInvitation, .rcvAborted: - receiveFileIfValidSize(file: file, receiveFile: receiveFile) - case .rcvAccepted: - switch file.fileProtocol { - case .xftp: - AlertManager.shared.showAlertMsg( - title: "Waiting for video", - message: "Video will be received when your contact completes uploading it." - ) - case .smp: - AlertManager.shared.showAlertMsg( - title: "Waiting for video", - message: "Video will be received when your contact is online, please wait or check later!" - ) - case .local: () - } - case .rcvTransfer: () // ? - case .rcvComplete: () // ? - case .rcvCancelled: () // TODO - default: () - } + if let file, let preview { + if let urlDecrypted { + if smallView { + smallVideoView(urlDecrypted, file, preview) + } else if let player { + videoView(player, urlDecrypted, file, preview, duration) } + } else if file.loaded { + if smallView { + smallVideoViewEncrypted(file, preview) + } else { + videoViewEncrypted(file, preview, duration) + } + } else { + Group { if smallView { smallViewImageView(preview, file) } else { imageView(preview) } } + .simultaneousGesture(TapGesture().onEnded { + switch file.fileStatus { + case .rcvInvitation, .rcvAborted: + receiveFileIfValidSize(file: file, receiveFile: receiveFile) + case .rcvAccepted: + switch file.fileProtocol { + case .xftp: + AlertManager.shared.showAlertMsg( + title: "Waiting for video", + message: "Video will be received when your contact completes uploading it." + ) + case .smp: + AlertManager.shared.showAlertMsg( + title: "Waiting for video", + message: "Video will be received when your contact is online, please wait or check later!" + ) + case .local: () + } + case .rcvTransfer: () // ? + case .rcvComplete: () // ? + case .rcvCancelled: () // TODO + default: () + } + }) + } } if !smallView { durationProgress() } } if !blurred, let file, showDownloadButton(file.fileStatus) { - if !smallView { - Button { - receiveFileIfValidSize(file: file, receiveFile: receiveFile) - } label: { - playPauseIcon("play.fill") - } - } else if !file.showStatusIconInSmallView { + if !smallView || !file.showStatusIconInSmallView { playPauseIcon("play.fill") - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { receiveFileIfValidSize(file: file, receiveFile: receiveFile) - } + }) } } } @@ -151,27 +151,26 @@ struct CIVideoView: View { ZStack(alignment: .center) { let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local) imageView(defaultPreview) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { decrypt(file: file) { showFullScreenPlayer = urlDecrypted != nil } - } + }) .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } if !blurred { if !decryptionInProgress { - Button { - decrypt(file: file) { - if urlDecrypted != nil { - videoPlaying = true - player?.play() + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") + .simultaneousGesture(TapGesture().onEnded { + decrypt(file: file) { + if urlDecrypted != nil { + videoPlaying = true + player?.play() + } } - } - } label: { - playPauseIcon(canBePlayed ? "play.fill" : "play.slash") - } - .disabled(!canBePlayed) + }) + .disabled(!canBePlayed) } else { videoDecryptionProgress() } @@ -194,29 +193,30 @@ struct CIVideoView: View { } } .modifier(PrivacyBlur(enabled: !videoPlaying, blurred: $blurred)) - .onTapGesture { - switch player.timeControlStatus { - case .playing: - player.pause() - videoPlaying = false - case .paused: - if canBePlayed { - showFullScreenPlayer = true + .if(!blurred) { v in + v.simultaneousGesture(TapGesture().onEnded { + switch player.timeControlStatus { + case .playing: + player.pause() + videoPlaying = false + case .paused: + if canBePlayed { + showFullScreenPlayer = true + } + default: () } - default: () - } + }) } .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } if !videoPlaying && !blurred { - Button { - m.stopPreviousRecPlay = url - player.play() - } label: { - playPauseIcon(canBePlayed ? "play.fill" : "play.slash") - } - .disabled(!canBePlayed) + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") + .simultaneousGesture(TapGesture().onEnded { + m.stopPreviousRecPlay = url + player.play() + }) + .disabled(!canBePlayed) } } fileStatusIcon() @@ -235,7 +235,7 @@ struct CIVideoView: View { return ZStack(alignment: .topLeading) { let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local) smallViewImageView(preview, file) - .onTapGesture { + .onTapGesture { // this is shown in chat list, where onTapGesture works decrypt(file: file) { showFullScreenPlayer = urlDecrypted != nil } @@ -256,7 +256,7 @@ struct CIVideoView: View { private func smallVideoView(_ url: URL, _ file: CIFile, _ preview: UIImage) -> some View { return ZStack(alignment: .topLeading) { smallViewImageView(preview, file) - .onTapGesture { + .onTapGesture { // this is shown in chat list, where onTapGesture works showFullScreenPlayer = true } .onChange(of: m.activeCallViewIsCollapsed) { _ in @@ -292,30 +292,22 @@ struct CIVideoView: View { .clipShape(Circle()) } - private func durationProgress() -> some View { - HStack { - Text("\(durationText(videoPlaying ? progress : duration))") - .foregroundColor(.white) - .font(.caption) - .padding(.vertical, 3) - .padding(.horizontal, 6) - .background(Color.black.opacity(0.35)) - .cornerRadius(10) - .padding([.top, .leading], 6) - - if let file = chatItem.file, !videoPlaying { - Text("\(ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary))") - .foregroundColor(.white) - .font(.caption) - .padding(.vertical, 3) - .padding(.horizontal, 6) - .background(Color.black.opacity(0.35)) - .cornerRadius(10) - .padding(.top, 6) - } + private var fileSizeString: String { + if let file = chatItem.file, !videoPlaying { + " " + ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) + } else { + "" } } + private func durationProgress() -> some View { + Text((durationText(videoPlaying ? progress : duration)) + fileSizeString) + .invertedForegroundStyle() + .font(.caption) + .padding(.vertical, 6) + .padding(.horizontal, 12) + } + private func imageView(_ img: UIImage) -> some View { let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth return ZStack(alignment: .topTrailing) { @@ -362,20 +354,14 @@ struct CIVideoView: View { case .sndCancelled: fileIcon("xmark", 10, 13) case let .sndError(sndFileError): fileIcon("xmark", 10, 13) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(sndFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(sndFileError) + }) case let .sndWarning(sndFileError): fileIcon("exclamationmark.triangle.fill", 10, 13) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(sndFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(sndFileError, temporary: true) + }) case .rcvInvitation: fileIcon("arrow.down", 10, 13) case .rcvAccepted: fileIcon("ellipsis", 14, 11) case let .rcvTransfer(rcvProgress, rcvTotal): @@ -389,20 +375,14 @@ struct CIVideoView: View { case .rcvCancelled: fileIcon("xmark", 10, 13) case let .rcvError(rcvFileError): fileIcon("xmark", 10, 13) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(rcvFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(rcvFileError) + }) case let .rcvWarning(rcvFileError): fileIcon("exclamationmark.triangle.fill", 10, 13) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(rcvFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(rcvFileError, temporary: true) + }) case .invalid: fileIcon("questionmark", 10, 13) } } @@ -411,9 +391,9 @@ struct CIVideoView: View { private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View { Image(systemName: icon) .resizable() + .invertedForegroundStyle() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(.white) .padding(smallView ? 0 : padding) } @@ -428,10 +408,8 @@ struct CIVideoView: View { private func progressCircle(_ progress: Int64, _ total: Int64) -> some View { Circle() .trim(from: 0, to: Double(progress) / Double(total)) - .stroke( - Color(uiColor: .white), - style: StrokeStyle(lineWidth: 2) - ) + .stroke(style: StrokeStyle(lineWidth: 2)) + .invertedForegroundStyle() .rotationEffect(.degrees(-90)) .frame(width: 16, height: 16) .padding([.trailing, .top], smallView ? 0 : 11) @@ -451,7 +429,7 @@ struct CIVideoView: View { Color.black.edgesIgnoringSafeArea(.all) VideoPlayer(player: fullPlayer) .overlay(alignment: .topLeading, content: { - Button(action: { showFullScreenPlayer = false }, + Button(action: { showFullScreenPlayer = false }, // this is used in full screen player, Button works here label: { Image(systemName: "multiply") .resizable() diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index 45a20f03bd..715e606a74 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -168,20 +168,14 @@ struct VoiceMessagePlayer: View { case .sndCancelled: playbackButton() case let .sndError(sndFileError): fileStatusIcon("multiply", 14) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(sndFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(sndFileError) + }) case let .sndWarning(sndFileError): fileStatusIcon("exclamationmark.triangle.fill", 16) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(sndFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(sndFileError, temporary: true) + }) case .rcvInvitation: downloadButton(recordingFile, "play.fill") case .rcvAccepted: loadingIcon() case .rcvTransfer: loadingIcon() @@ -190,20 +184,14 @@ struct VoiceMessagePlayer: View { case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) case let .rcvError(rcvFileError): fileStatusIcon("multiply", 14) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("File error"), - message: Text(rcvFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(rcvFileError) + }) case let .rcvWarning(rcvFileError): fileStatusIcon("exclamationmark.triangle.fill", 16) - .onTapGesture { - AlertManager.shared.showAlert(Alert( - title: Text("Temporary file error"), - message: Text(rcvFileError.errorInfo) - )) - } + .simultaneousGesture(TapGesture().onEnded { + showFileErrorAlert(rcvFileError, temporary: true) + }) case .invalid: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) } } else { @@ -267,59 +255,29 @@ struct VoiceMessagePlayer: View { } } - @ViewBuilder private func playbackButton() -> some View { - if sizeMultiplier != 1 { - switch playbackState { - case .noPlayback: - playPauseIcon("play.fill", theme.colors.primary) - .onTapGesture { - if let recordingSource = getLoadedFileSource(recordingFile) { - startPlayback(recordingSource) - } - } - case .playing: - playPauseIcon("pause.fill", theme.colors.primary) - .onTapGesture { - audioPlayer?.pause() - playbackState = .paused - notifyStateChange() - } - case .paused: - playPauseIcon("play.fill", theme.colors.primary) - .onTapGesture { - audioPlayer?.play() - playbackState = .playing - notifyStateChange() - } - } - } else { - switch playbackState { - case .noPlayback: - Button { + private func playbackButton() -> some View { + let icon = switch playbackState { + case .noPlayback: "play.fill" + case .playing: "pause.fill" + case .paused: "play.fill" + } + return playPauseIcon(icon, theme.colors.primary) + .simultaneousGesture(TapGesture().onEnded { _ in + switch playbackState { + case .noPlayback: if let recordingSource = getLoadedFileSource(recordingFile) { startPlayback(recordingSource) } - } label: { - playPauseIcon("play.fill", theme.colors.primary) - } - case .playing: - Button { + case .playing: audioPlayer?.pause() playbackState = .paused notifyStateChange() - } label: { - playPauseIcon("pause.fill", theme.colors.primary) - } - case .paused: - Button { + case .paused: audioPlayer?.play() playbackState = .playing notifyStateChange() - } label: { - playPauseIcon("play.fill", theme.colors.primary) } - } - } + }) } private func playPauseIcon(_ image: String, _ color: Color/* = .accentColor*/) -> some View { @@ -341,28 +299,14 @@ struct VoiceMessagePlayer: View { } private func downloadButton(_ recordingFile: CIFile, _ icon: String) -> some View { - Group { - if sizeMultiplier != 1 { - playPauseIcon(icon, theme.colors.primary) - .onTapGesture { - Task { - if let user = chatModel.currentUser { - await receiveFile(user: user, fileId: recordingFile.fileId) - } - } + playPauseIcon(icon, theme.colors.primary) + .simultaneousGesture(TapGesture().onEnded { + Task { + if let user = chatModel.currentUser { + await receiveFile(user: user, fileId: recordingFile.fileId) } - } else { - Button { - Task { - if let user = chatModel.currentUser { - await receiveFile(user: user, fileId: recordingFile.fileId) - } - } - } label: { - playPauseIcon(icon, theme.colors.primary) } - } - } + }) } func notifyStateChange() { @@ -442,6 +386,7 @@ struct VoiceMessagePlayer: View { } } +@inline(__always) func voiceMessageSizeBasedOnSquareSize(_ squareSize: CGFloat) -> CGFloat { let squareToCircleRatio = 0.935 return squareSize + squareSize * (1 - squareToCircleRatio) @@ -458,10 +403,12 @@ class VoiceItemState { self.playbackTime = playbackTime } + @inline(__always) static func id(_ chat: Chat, _ chatItem: ChatItem) -> String { "\(chat.id) \(chatItem.id)" } + @inline(__always) static func id(_ chatInfo: ChatInfo, _ chatItem: ChatItem) -> String { "\(chatInfo.id) \(chatItem.id)" } @@ -510,10 +457,10 @@ struct CIVoiceView_Previews: PreviewProvider { duration: 30, allowMenu: Binding.constant(true) ) - ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in }, allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItemId: { _ in }, allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }, allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, scrollToItemId: { _ in }, allowMenu: .constant(true)) } .previewLayout(.fixed(width: 360, height: 360)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift index 64a7f29a25..f4e2a4135a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift @@ -92,12 +92,13 @@ struct FramedCIVoiceView_Previews: PreviewProvider { file: CIFile.getSample(fileStatus: .sndComplete) ) Group { - ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false)) + ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, scrollToItemId: { _ in }) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 360)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 313ec0d419..b27d266d8a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -12,11 +12,10 @@ import SimpleXChat struct FramedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @EnvironmentObject var scrollModel: ReverseListScrollModel @ObservedObject var chat: Chat var chatItem: ChatItem + var scrollToItemId: (ChatItem.ID) -> Void var preview: UIImage? - @Binding var revealed: Bool var maxWidth: CGFloat = .infinity @State var msgWidth: CGFloat = 0 var imgWidth: CGFloat? = nil @@ -24,14 +23,22 @@ struct FramedItemView: View { @State private var useWhiteMetaColor: Bool = false @State var showFullScreenImage = false @Binding var allowMenu: Bool - @State private var showSecrets = false - @State private var showQuoteSecrets = false @State private var showFullscreenGallery: Bool = false var body: some View { let v = ZStack(alignment: .bottomTrailing) { VStack(alignment: .leading, spacing: 0) { - if let di = chatItem.meta.itemDeleted { + if chatItem.isReport { + if chatItem.meta.itemDeleted == nil { + let txt = chatItem.chatDir.sent ? + Text("Only you and moderators see it") : + Text("Only sender and moderators see it") + + framedItemHeader(icon: "flag", iconColor: .red, caption: txt.italic()) + } else { + framedItemHeader(icon: "flag", caption: Text("archived report").italic()) + } + } else if let di = chatItem.meta.itemDeleted { switch di { case let .moderated(_, byGroupMember): framedItemHeader(icon: "flag", caption: Text("moderated by \(byGroupMember.displayName)").italic()) @@ -48,42 +55,51 @@ struct FramedItemView: View { if let qi = chatItem.quotedItem { ciQuoteView(qi) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) { withAnimation { - scrollModel.scrollToItem(id: ci.id) + scrollToItemId(ci.id) } + } else if let id = qi.itemId { + scrollToItemId(id) + } else { + showQuotedItemDoesNotExistAlert() } - } + }) } else if let itemForwarded = chatItem.meta.itemForwarded { framedItemHeader(icon: "arrowshape.turn.up.forward", caption: Text(itemForwarded.text(chat.chatInfo.chatType)).italic(), pad: true) } - ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: framedMsgContentView) + ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: framedMsgContentView) .padding(chatItem.content.msgContent != nil ? 0 : 4) .overlay(DetermineWidth()) } - if chatItem.content.msgContent != nil { - CIMetaView(chat: chat, chatItem: chatItem, metaColor: useWhiteMetaColor ? Color.white : theme.colors.secondary) - .padding(.horizontal, 12) - .padding(.bottom, 6) - .overlay(DetermineWidth()) - .accessibilityLabel("") + if let content = chatItem.content.msgContent { + CIMetaView( + chat: chat, + chatItem: chatItem, + metaColor: theme.colors.secondary, + invertedMaterial: useWhiteMetaColor + ) + .padding(.horizontal, 12) + .padding(.bottom, 6) + .overlay(DetermineWidth()) + .accessibilityLabel("") } } - .background(chatItemFrameColorMaybeImageOrVideo(chatItem, theme)) + .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } if let (title, text) = chatItem.meta.itemStatus.statusInfo { - v.onTapGesture { + v.simultaneousGesture(TapGesture().onEnded { AlertManager.shared.showAlert( Alert( title: Text(title), message: Text(text) ) ) - } + }) } else { v } @@ -103,7 +119,7 @@ struct FramedItemView: View { } else { switch (chatItem.content.msgContent) { case let .image(text, _): - CIImageView(chatItem: chatItem, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery) + CIImageView(chatItem: chatItem, scrollToItemId: scrollToItemId, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery) .overlay(DetermineWidth()) if text == "" && !chatItem.meta.isLive { Color.clear @@ -140,6 +156,8 @@ struct FramedItemView: View { } case let .file(text): ciFileView(chatItem, text) + case let .report(text, reason): + ciMsgContentView(chatItem, txtPrefix: reason.attrString) case let .link(_, preview): CILinkView(linkPreview: preview) ciMsgContentView(chatItem) @@ -155,13 +173,14 @@ struct FramedItemView: View { } } - @ViewBuilder func framedItemHeader(icon: String? = nil, caption: Text, pad: Bool = false) -> some View { + @ViewBuilder func framedItemHeader(icon: String? = nil, iconColor: Color? = nil, caption: Text, pad: Bool = false) -> some View { let v = HStack(spacing: 6) { if let icon = icon { Image(systemName: icon) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) + .foregroundColor(iconColor ?? theme.colors.secondary) } caption .font(.caption) @@ -182,10 +201,11 @@ struct FramedItemView: View { } @ViewBuilder private func ciQuoteView(_ qi: CIQuote) -> some View { + let backgroundColor = chatItemFrameContextColor(chatItem, theme) let v = ZStack(alignment: .topTrailing) { switch (qi.content) { case let .image(_, image): - if let uiImage = UIImage(base64Encoded: image) { + if let uiImage = imageFromBase64(image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) @@ -197,7 +217,7 @@ struct FramedItemView: View { ciQuotedMsgView(qi) } case let .video(_, image, _): - if let uiImage = UIImage(base64Encoded: image) { + if let uiImage = imageFromBase64(image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) @@ -223,8 +243,8 @@ struct FramedItemView: View { // if enable this always, size of the framed voice message item will be incorrect after end of playback .overlay { if case .voice = chatItem.content.msgContent {} else { DetermineWidth() } } .frame(minWidth: msgWidth, alignment: .leading) - .background(chatItemFrameContextColor(chatItem, theme)) - + .background(backgroundColor) + .environment(\.containerBackground, UIColor(backgroundColor)) if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth { v.frame(maxWidth: mediaWidth, alignment: .leading) } else { @@ -238,7 +258,7 @@ struct FramedItemView: View { VStack(alignment: .leading, spacing: 2) { Text(sender) .font(.caption) - .foregroundColor(theme.colors.secondary) + .foregroundColor(qi.chatDir == .groupSnd ? .accentColor : theme.colors.secondary) .lineLimit(1) ciQuotedMsgTextView(qi, lines: 2) } @@ -250,14 +270,12 @@ struct FramedItemView: View { .padding(.top, 6) .padding(.horizontal, 12) } - + + @inline(__always) private func ciQuotedMsgTextView(_ qi: CIQuote, lines: Int) -> some View { - toggleSecrets(qi.formattedText, $showQuoteSecrets, - MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, showSecrets: showQuoteSecrets) - .lineLimit(lines) - .font(.subheadline) - .padding(.bottom, 6) - ) + MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, textStyle: .subheadline) + .lineLimit(lines) + .padding(.bottom, 6) } private func ciQuoteIconView(_ image: String) -> some View { @@ -277,18 +295,22 @@ struct FramedItemView: View { } } - @ViewBuilder private func ciMsgContentView(_ ci: ChatItem) -> some View { + @ViewBuilder private func ciMsgContentView(_ ci: ChatItem, txtPrefix: NSAttributedString? = nil) -> some View { let text = ci.meta.isLive ? ci.content.msgContent?.text ?? ci.text : ci.text let rtl = isRightToLeft(text) let ft = text == "" ? [] : ci.formattedText - let v = toggleSecrets(ft, $showSecrets, MsgContentView( + let v = MsgContentView( chat: chat, text: text, formattedText: ft, + textStyle: .body, meta: ci.meta, + mentions: ci.mentions, + userMemberId: chat.chatInfo.groupInfo?.membership.memberId, rightToLeft: rtl, - showSecrets: showSecrets - )) + prefix: txtPrefix + ) + .environment(\.containerBackground, UIColor(chatItemFrameColor(ci, theme))) .multilineTextAlignment(rtl ? .trailing : .leading) .padding(.vertical, 6) .padding(.horizontal, 12) @@ -319,13 +341,12 @@ struct FramedItemView: View { return videoWidth } } -} -@ViewBuilder func toggleSecrets(_ ft: [FormattedText]?, _ showSecrets: Binding, _ v: V) -> some View { - if let ft = ft, ft.contains(where: { $0.isSecret }) { - v.onTapGesture { showSecrets.wrappedValue.toggle() } - } else { - v + private func showQuotedItemDoesNotExistAlert() { + AlertManager.shared.showAlertMsg( + title: "No message", + message: "This message was deleted or not received yet." + ) } } @@ -366,14 +387,14 @@ func chatItemFrameContextColor(_ ci: ChatItem, _ theme: AppTheme) -> Color { struct FramedItemView_Previews: PreviewProvider { static var previews: some View { Group{ - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) } .previewLayout(.fixed(width: 360, height: 200)) } @@ -382,17 +403,18 @@ struct FramedItemView_Previews: PreviewProvider { struct FramedItemView_Edited_Previews: PreviewProvider { static var previews: some View { Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 200)) } } @@ -400,17 +422,18 @@ struct FramedItemView_Edited_Previews: PreviewProvider { struct FramedItemView_Deleted_Previews: PreviewProvider { static var previews: some View { Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 200)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift index a80c5412b6..10e5efa298 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift @@ -13,8 +13,8 @@ import AVKit struct FullScreenMediaView: View { @EnvironmentObject var m: ChatModel - @EnvironmentObject var scrollModel: ReverseListScrollModel @State var chatItem: ChatItem + var scrollToItemId: ((ChatItem.ID) -> Void)? @State var image: UIImage? @State var player: AVPlayer? = nil @State var url: URL? = nil @@ -71,7 +71,7 @@ struct FullScreenMediaView: View { let w = abs(t.width) if t.height > 60 && t.height > w * 2 { showView = false - scrollModel.scrollToItem(id: chatItem.id) + scrollToItemId?(chatItem.id) } else if w > 60 && w > abs(t.height) * 2 && !scrolling { let previous = t.width > 0 scrolling = true @@ -126,7 +126,7 @@ struct FullScreenMediaView: View { .scaledToFit() } } - .onTapGesture { showView = false } + .onTapGesture { showView = false } // this is used in full screen view, onTapGesture works } private func videoView( _ player: AVPlayer, _ url: URL) -> some View { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift index 822dda4d06..47a30f6cf3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift @@ -31,8 +31,8 @@ struct IntegrityErrorItemView: View { case .msgBadHash: AlertManager.shared.showAlert(Alert( title: Text("Bad message hash"), - message: Text("The hash of the previous message is different.") + Text("\n") + - Text(decryptErrorReason) + Text("\n") + + message: Text("The hash of the previous message is different.") + textNewLine + + Text(decryptErrorReason) + textNewLine + Text("Please report it to the developers.") )) case .msgBadId: msgBadIdAlert() @@ -47,7 +47,7 @@ struct IntegrityErrorItemView: View { message: Text(""" The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. - """) + Text("\n") + + """) + textNewLine + Text("Please report it to the developers.") )) } @@ -69,9 +69,9 @@ struct CIMsgError: View { } .padding(.leading, 12) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) - .onTapGesture(perform: onTap) + .simultaneousGesture(TapGesture().onEnded(onTap)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index 25e06b9ea4..87a9b2ce61 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -12,17 +12,17 @@ import SimpleXChat struct MarkedDeletedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.revealed) var revealed: Bool @ObservedObject var chat: Chat var chatItem: ChatItem - @Binding var revealed: Bool var body: some View { - (Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText) + (Text(mergedMarkedDeletedText).italic() + textSpace + chatItem.timestampText) .font(.caption) .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(chatItem, theme)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) } @@ -67,11 +67,15 @@ struct MarkedDeletedItemView: View { // same texts are in markedDeletedText in ChatPreviewView, but it returns String; // can be refactored into a single function if functions calling these are changed to return same type var markedDeletedText: LocalizedStringKey { - switch chatItem.meta.itemDeleted { - case let .moderated(_, byGroupMember): "moderated by \(byGroupMember.displayName)" - case .blocked: "blocked" - case .blockedByAdmin: "blocked by admin" - case .deleted, nil: "marked deleted" + if chatItem.meta.itemDeleted != nil, chatItem.isReport { + "archived report" + } else { + switch chatItem.meta.itemDeleted { + case let .moderated(_, byGroupMember): "moderated by \(byGroupMember.displayName)" + case .blocked: "blocked" + case .blockedByAdmin: "blocked by admin" + case .deleted, nil: "marked deleted" + } } } } @@ -79,7 +83,10 @@ struct MarkedDeletedItemView: View { struct MarkedDeletedItemView_Previews: PreviewProvider { static var previews: some View { Group { - MarkedDeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true)) + MarkedDeletedItemView( + chat: Chat.sampleData, + chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)) + ).environment(\.revealed, true) } .previewLayout(.fixed(width: 360, height: 200)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 999f99b294..e04584dfff 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -11,49 +11,74 @@ import SimpleXChat let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1) -private let noTyping = Text(" ") - -private let typingIndicators: [Text] = [ - (typing(.black) + typing() + typing()), - (typing(.bold) + typing(.black) + typing()), - (typing() + typing(.bold) + typing(.black)), - (typing() + typing() + typing(.bold)) -] - -private func typing(_ w: Font.Weight = .light) -> Text { - Text(".").fontWeight(w) +private func typing(_ theme: AppTheme, _ descr: UIFontDescriptor, _ ws: [UIFont.Weight]) -> NSMutableAttributedString { + let res = NSMutableAttributedString() + for w in ws { + res.append(NSAttributedString(string: ".", attributes: [ + .font: UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: w), + .kern: -2 as NSNumber, + .foregroundColor: UIColor(theme.colors.secondary) + ])) + } + return res } struct MsgContentView: View { @ObservedObject var chat: Chat + @Environment(\.showTimestamp) var showTimestamp: Bool + @Environment(\.containerBackground) var containerBackground: UIColor @EnvironmentObject var theme: AppTheme var text: String var formattedText: [FormattedText]? = nil + var textStyle: UIFont.TextStyle var sender: String? = nil var meta: CIMeta? = nil + var mentions: [String: CIMention]? = nil + var userMemberId: String? = nil var rightToLeft = false - var showSecrets: Bool + var prefix: NSAttributedString? = nil + @State private var showSecrets: Set = [] @State private var typingIdx = 0 @State private var timer: Timer? + @State private var typingIndicators: [NSAttributedString] = [] + @State private var noTyping = NSAttributedString(string: " ") + @State private var phase: CGFloat = 0 @AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false var body: some View { + let v = msgContentView() if meta?.isLive == true { - msgContentView() - .onAppear { switchTyping() } + v.onAppear { + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + noTyping = NSAttributedString(string: " ", attributes: [ + .font: UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: .regular), + .kern: -2 as NSNumber, + .foregroundColor: UIColor(theme.colors.secondary) + ]) + switchTyping() + } .onDisappear(perform: stopTyping) .onChange(of: meta?.isLive, perform: switchTyping) .onChange(of: meta?.recent, perform: switchTyping) } else { - msgContentView() + v } } private func switchTyping(_: Bool? = nil) { if let meta = meta, meta.isLive && meta.recent { + if typingIndicators.isEmpty { + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + typingIndicators = [ + typing(theme, descr, [.black, .light, .light]), + typing(theme, descr, [.bold, .black, .light]), + typing(theme, descr, [.light, .bold, .black]), + typing(theme, descr, [.light, .light, .bold]) + ] + } timer = timer ?? Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { _ in - typingIdx = (typingIdx + 1) % typingIndicators.count + typingIdx = typingIdx + 1 } } else { stopTyping() @@ -63,95 +88,276 @@ struct MsgContentView: View { private func stopTyping() { timer?.invalidate() timer = nil + typingIdx = 0 } - private func msgContentView() -> Text { - var v = messageText(text, formattedText, sender, showSecrets: showSecrets, secondaryColor: theme.colors.secondary) + @inline(__always) + private func msgContentView() -> some View { + let r = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: containerBackground, prefix: prefix) + let s = r.string + let t: Text if let mt = meta { if mt.isLive { - v = v + typingIndicator(mt.recent) + s.append(typingIndicator(mt.recent)) } - v = v + reserveSpaceForMeta(mt) + t = Text(AttributedString(s)) + reserveSpaceForMeta(mt) + } else { + t = Text(AttributedString(s)) } - return v + return msgTextResultView(r, t, showSecrets: $showSecrets) } - private func typingIndicator(_ recent: Bool) -> Text { - return (recent ? typingIndicators[typingIdx] : noTyping) - .font(.body.monospaced()) - .kerning(-2) - .foregroundColor(theme.colors.secondary) + @inline(__always) + private func typingIndicator(_ recent: Bool) -> NSAttributedString { + recent && !typingIndicators.isEmpty + ? typingIndicators[typingIdx % 4] + : noTyping } + @inline(__always) private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { - (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + (rightToLeft ? textNewLine : Text(verbatim: " ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } -func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false, showSecrets: Bool, secondaryColor: Color) -> Text { - let s = text - var res: Text - if let ft = formattedText, ft.count > 0 && ft.count <= 200 { - res = formatText(ft[0], preview, showSecret: showSecrets) - var i = 1 - while i < ft.count { - res = res + formatText(ft[i], preview, showSecret: showSecrets) - i = i + 1 - } - } else { - res = Text(s) - } - - if let i = icon { - res = Text(Image(systemName: i)).foregroundColor(secondaryColor) + Text(" ") + res - } - - if let s = sender { - let t = Text(s) - return (preview ? t : t.fontWeight(.medium)) + Text(": ") + res - } else { - return res - } +func msgTextResultView(_ r: MsgTextResult, _ t: Text, showSecrets: Binding>? = nil) -> some View { + t.if(r.hasSecrets, transform: hiddenSecretsView) + .if(r.handleTaps) { $0.overlay(handleTextTaps(r.string, showSecrets: showSecrets)) } } -private func formatText(_ ft: FormattedText, _ preview: Bool, showSecret: Bool) -> Text { - let t = ft.text - if let f = ft.format { - switch (f) { - case .bold: return Text(t).bold() - case .italic: return Text(t).italic() - case .strikeThrough: return Text(t).strikethrough() - case .snippet: return Text(t).font(.body.monospaced()) - case .secret: return - showSecret - ? Text(t) - : Text(AttributedString(t, attributes: AttributeContainer([ - .foregroundColor: UIColor.clear as Any, - .backgroundColor: UIColor.secondarySystemFill as Any - ]))) - case let .colored(color): return Text(t).foregroundColor(color.uiColor) - case .uri: return linkText(t, t, preview, prefix: "") - case let .simplexLink(linkType, simplexUri, smpHosts): - switch privacySimplexLinkModeDefault.get() { - case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "") - case .full: return linkText(t, simplexUri, preview, prefix: "") - case .browser: return linkText(t, simplexUri, preview, prefix: "") +@inline(__always) +private func handleTextTaps(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { + return GeometryReader { g in + Rectangle() + .fill(Color.clear) + .contentShape(Rectangle()) + .simultaneousGesture(DragGesture(minimumDistance: 0).onEnded { event in + let t = event.translation + if t.width * t.width + t.height * t.height > 100 { return } + let framesetter = CTFramesetterCreateWithAttributedString(s as CFAttributedString) + let path = CGPath(rect: CGRect(origin: .zero, size: g.size), transform: nil) + let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, s.length), path, nil) + let point = CGPoint(x: event.location.x, y: g.size.height - event.location.y) // Flip y for UIKit + var index: CFIndex? + if let lines = CTFrameGetLines(frame) as? [CTLine] { + var origins = [CGPoint](repeating: .zero, count: lines.count) + CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &origins) + for i in 0 ..< lines.count { + let bounds = CTLineGetBoundsWithOptions(lines[i], .useOpticalBounds) + if bounds.offsetBy(dx: origins[i].x, dy: origins[i].y).contains(point) { + index = CTLineGetStringIndexForPosition(lines[i], point) + break + } + } + } + if let index, let (url, browser) = attributedStringLink(s, for: index) { + if browser { + openBrowserAlert(uri: url) + } else { + UIApplication.shared.open(url) + } + } + }) + } + + func attributedStringLink(_ s: NSAttributedString, for index: CFIndex) -> (URL, Bool)? { + var linkURL: URL? + var browser: Bool = false + s.enumerateAttributes(in: NSRange(location: 0, length: s.length)) { attrs, range, stop in + if index >= range.location && index < range.location + range.length { + if let url = attrs[linkAttrKey] as? NSURL { + linkURL = url.absoluteURL + browser = attrs[webLinkAttrKey] != nil + } else if let showSecrets, let i = attrs[secretAttrKey] as? Int { + if showSecrets.wrappedValue.contains(i) { + showSecrets.wrappedValue.remove(i) + } else { + showSecrets.wrappedValue.insert(i) + } + } + stop.pointee = true } - case .email: return linkText(t, t, preview, prefix: "mailto:") - case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:") } - } else { - return Text(t) + return if let linkURL { (linkURL, browser) } else { nil } } } -private func linkText(_ s: String, _ link: String, _ preview: Bool, prefix: String, color: Color = Color(uiColor: uiLinkColor), uiColor: UIColor = uiLinkColor) -> Text { - preview - ? Text(s).foregroundColor(color).underline(color: color) - : Text(AttributedString(s, attributes: AttributeContainer([ - .link: NSURL(string: prefix + link) as Any, - .foregroundColor: uiColor as Any - ]))).underline() +func hiddenSecretsView(_ v: V) -> some View { + v.overlay( + GeometryReader { g in + let size = (g.size.width + g.size.height) / 1.4142 + Image("vertical_logo") + .resizable(resizingMode: .tile) + .frame(width: size, height: size) + .rotationEffect(.degrees(45), anchor: .center) + .position(x: g.size.width / 2, y: g.size.height / 2) + .clipped() + .saturation(0.65) + .opacity(0.35) + } + .mask(v) + ) +} + +private let linkAttrKey = NSAttributedString.Key("chat.simplex.app.link") + +private let webLinkAttrKey = NSAttributedString.Key("chat.simplex.app.webLink") + +private let secretAttrKey = NSAttributedString.Key("chat.simplex.app.secret") + +typealias MsgTextResult = (string: NSMutableAttributedString, hasSecrets: Bool, handleTaps: Bool) + +func messageText( + _ text: String, + _ formattedText: [FormattedText]?, + textStyle: UIFont.TextStyle = .body, + sender: String?, + preview: Bool = false, + mentions: [String: CIMention]?, + userMemberId: String?, + showSecrets: Set?, + backgroundColor: UIColor, + prefix: NSAttributedString? = nil +) -> MsgTextResult { + let res = NSMutableAttributedString() + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle) + let font = UIFont.preferredFont(forTextStyle: textStyle) + let plain: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.label + ] + let secretColor = backgroundColor.withAlphaComponent(1) + var link: [NSAttributedString.Key: Any]? + var hasSecrets = false + var handleTaps = false + + if let sender { + if preview { + res.append(NSAttributedString(string: sender + ": ", attributes: plain)) + } else { + var attrs = plain + attrs[.font] = UIFont(descriptor: descr.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.medium]]), size: descr.pointSize) + res.append(NSAttributedString(string: sender, attributes: attrs)) + res.append(NSAttributedString(string: ": ", attributes: plain)) + } + } + + if let prefix { + res.append(prefix) + } + + if let fts = formattedText, fts.count > 0 { + var bold: UIFont? + var italic: UIFont? + var snippet: UIFont? + var mention: UIFont? + var secretIdx: Int = 0 + for ft in fts { + var t = ft.text + var attrs = plain + switch (ft.format) { + case .bold: + bold = bold ?? UIFont(descriptor: descr.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.bold]]), size: descr.pointSize) + attrs[.font] = bold + case .italic: + italic = italic ?? UIFont(descriptor: descr.withSymbolicTraits(.traitItalic) ?? descr, size: descr.pointSize) + attrs[.font] = italic + case .strikeThrough: + attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue + case .snippet: + snippet = snippet ?? UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: .regular) + attrs[.font] = snippet + case .secret: + if let showSecrets { + if !showSecrets.contains(secretIdx) { + attrs[.foregroundColor] = UIColor.clear + attrs[.backgroundColor] = secretColor + } + attrs[secretAttrKey] = secretIdx + secretIdx += 1 + handleTaps = true + } else { + attrs[.foregroundColor] = UIColor.clear + attrs[.backgroundColor] = secretColor + } + hasSecrets = true + case let .colored(color): + if let c = color.uiColor { + attrs[.foregroundColor] = UIColor(c) + } + case .uri: + attrs = linkAttrs() + if !preview { + let s = t.lowercased() + let link = s.hasPrefix("http://") || s.hasPrefix("https://") + ? t + : "https://" + t + attrs[linkAttrKey] = NSURL(string: link) + attrs[webLinkAttrKey] = true + handleTaps = true + } + case let .simplexLink(linkType, simplexUri, smpHosts): + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: simplexUri) + handleTaps = true + } + if case .description = privacySimplexLinkModeDefault.get() { + t = simplexLinkText(linkType, smpHosts) + } + case let .mention(memberName): + if let m = mentions?[memberName] { + mention = mention ?? UIFont(descriptor: descr.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold]]), size: descr.pointSize) + attrs[.font] = mention + if let ref = m.memberRef { + let name: String = if let alias = ref.localAlias, alias != "" { + "\(alias) (\(ref.displayName))" + } else { + ref.displayName + } + if m.memberId == userMemberId { + attrs[.foregroundColor] = UIColor.tintColor + } + t = mentionText(name) + } else { + t = mentionText(memberName) + } + } + case .email: + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: "mailto:" + ft.text) + handleTaps = true + } + case .phone: + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: "tel:" + t.replacingOccurrences(of: " ", with: "")) + handleTaps = true + } + case .none: () + } + res.append(NSAttributedString(string: t, attributes: attrs)) + } + } else { + res.append(NSMutableAttributedString(string: text, attributes: plain)) + } + + return (string: res, hasSecrets: hasSecrets, handleTaps: handleTaps) + + func linkAttrs() -> [NSAttributedString.Key: Any] { + link = link ?? [ + .font: font, + .foregroundColor: uiLinkColor, + .underlineStyle: NSUnderlineStyle.single.rawValue + ] + return link! + } +} + +@inline(__always) +private func mentionText(_ name: String) -> String { + name.contains(" @") ? "@'\(name)'" : "@\(name)" } func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String { @@ -165,9 +371,9 @@ struct MsgContentView_Previews: PreviewProvider { chat: Chat.sampleData, text: chatItem.text, formattedText: chatItem.formattedText, + textStyle: .body, sender: chatItem.memberDisplayName, - meta: chatItem.meta, - showSecrets: false + meta: chatItem.meta ) .environmentObject(Chat.sampleData) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 32993d1a76..dfc620c402 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -14,12 +14,11 @@ struct ChatItemForwardingView: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss - var ci: ChatItem + var chatItems: [ChatItem] var fromChatInfo: ChatInfo @Binding var composeState: ComposeState @State private var searchText: String = "" - @FocusState private var searchFocused @State private var alert: SomeAlert? private let chatsToForwardTo = filterChatsToForwardTo(chats: ChatModel.shared.chats) @@ -42,12 +41,10 @@ struct ChatItemForwardingView: View { .alert(item: $alert) { $0.alert } } - @ViewBuilder private func forwardListView() -> some View { + private func forwardListView() -> some View { VStack(alignment: .leading) { if !chatsToForwardTo.isEmpty { List { - searchFieldView(text: $searchText, focussed: $searchFocused, theme.colors.onBackground, theme.colors.secondary) - .padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) } ForEach(chats) { chat in @@ -55,6 +52,7 @@ struct ChatItemForwardingView: View { .disabled(chatModel.deletedChats.contains(chat.chatInfo.id)) } } + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) .modifier(ThemedBackground(grouped: true)) } else { ZStack { @@ -73,11 +71,14 @@ struct ChatItemForwardingView: View { } @ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View { - let prohibited = chat.prohibitedByPref( - hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), - isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, - isVoice: ci.content.msgContent?.isVoice ?? false - ) + let prohibited = chatItems.map { ci in + chat.prohibitedByPref( + hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), + isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, + isVoice: ci.content.msgContent?.isVoice ?? false + ) + }.contains(true) + Button { if prohibited { alert = SomeAlert( @@ -93,10 +94,10 @@ struct ChatItemForwardingView: View { composeState = ComposeState( message: composeState.message, preview: composeState.linkPreview != nil ? composeState.preview : .noPreview, - contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo) + contextItem: .forwardingItems(chatItems: chatItems, fromChatInfo: fromChatInfo) ) } else { - composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo) + composeState = ComposeState.init(forwardingItems: chatItems, fromChatInfo: fromChatInfo) ItemsModel.shared.loadOpenChat(chat.id) } } @@ -123,7 +124,7 @@ struct ChatItemForwardingView: View { #Preview { ChatItemForwardingView( - ci: ChatItem.getSample(1, .directSnd, .now, "hello"), + chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")], fromChatInfo: .direct(contact: Contact.sampleData), composeState: Binding.constant(ComposeState(message: "hello")) ).environmentObject(CurrentColors.toAppTheme()) diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index f6a856dad1..cd75d1b0cd 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -14,6 +14,7 @@ struct ChatItemInfoView: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var theme: AppTheme var ci: ChatItem + var userMemberId: String? @Binding var chatItemInfo: ChatItemInfo? @State private var selection: CIInfoTab = .history @State private var alert: CIInfoViewAlert? = nil @@ -130,9 +131,9 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func details() -> some View { + private func details() -> some View { let meta = ci.meta - VStack(alignment: .leading, spacing: 16) { + return VStack(alignment: .leading, spacing: 16) { Text(title) .font(.largeTitle) .bold() @@ -196,7 +197,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func historyTab() -> some View { + private func historyTab() -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 ScrollView { @@ -226,12 +227,13 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { - VStack(alignment: .leading, spacing: 4) { - textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil) + private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { + let backgroundColor = chatItemFrameColor(ci, theme) + return VStack(alignment: .leading, spacing: 4) { + textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil, backgroundColor: UIColor(backgroundColor)) .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(ci, theme)) + .background(backgroundColor) .modifier(ChatItemClipped()) .contextMenu { if itemVersion.msgContent.text != "" { @@ -256,9 +258,9 @@ struct ChatItemInfoView: View { .frame(maxWidth: maxWidth, alignment: .leading) } - @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil) -> some View { + @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil, backgroundColor: UIColor) -> some View { if text != "" { - TextBubble(text: text, formattedText: formattedText, sender: sender) + TextBubble(text: text, formattedText: formattedText, sender: sender, mentions: ci.mentions, userMemberId: userMemberId, backgroundColor: backgroundColor) } else { Text("no text") .italic() @@ -271,14 +273,18 @@ struct ChatItemInfoView: View { var text: String var formattedText: [FormattedText]? var sender: String? = nil - @State private var showSecrets = false + var mentions: [String: CIMention]? + var userMemberId: String? + var backgroundColor: UIColor + @State private var showSecrets: Set = [] var body: some View { - toggleSecrets(formattedText, $showSecrets, messageText(text, formattedText, sender, showSecrets: showSecrets, secondaryColor: theme.colors.secondary)) + let r = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: backgroundColor) + return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) } } - @ViewBuilder private func quoteTab(_ qi: CIQuote) -> some View { + private func quoteTab(_ qi: CIQuote) -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 ScrollView { @@ -296,9 +302,10 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { - VStack(alignment: .leading, spacing: 4) { - textBubble(qi.text, qi.formattedText, qi.getSender(nil)) + private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { + let backgroundColor = quotedMsgFrameColor(qi, theme) + return VStack(alignment: .leading, spacing: 4) { + textBubble(qi.text, qi.formattedText, qi.getSender(nil), backgroundColor: UIColor(backgroundColor)) .padding(.horizontal, 12) .padding(.vertical, 6) .background(quotedMsgFrameColor(qi, theme)) @@ -331,7 +338,7 @@ struct ChatItemInfoView: View { : theme.appColors.receivedMessage } - @ViewBuilder private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View { + private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View { ScrollView { VStack(alignment: .leading, spacing: 16) { details() @@ -351,8 +358,9 @@ struct ChatItemInfoView: View { Button { Task { await MainActor.run { - ItemsModel.shared.loadOpenChat(forwardedFromItem.chatInfo.id) - dismiss() + ItemsModel.shared.loadOpenChat(forwardedFromItem.chatInfo.id) { + dismiss() + } } } } label: { @@ -368,7 +376,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func forwardedFromSender(_ forwardedFromItem: AChatItem) -> some View { + private func forwardedFromSender(_ forwardedFromItem: AChatItem) -> some View { HStack { ChatInfoImage(chat: Chat(chatInfo: forwardedFromItem.chatInfo), size: 48) .padding(.trailing, 6) @@ -399,7 +407,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { + private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { ScrollView { VStack(alignment: .leading, spacing: 16) { details() @@ -414,7 +422,7 @@ struct ChatItemInfoView: View { .frame(maxHeight: .infinity, alignment: .top) } - @ViewBuilder private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { + private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { LazyVStack(alignment: .leading, spacing: 12) { let mss = membersStatuses(memberDeliveryStatuses) if !mss.isEmpty { @@ -450,20 +458,8 @@ struct ChatItemInfoView: View { .foregroundColor(theme.colors.secondary).opacity(0.67) } let v = Group { - let (icon, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) - switch status { - case .rcvd: - ZStack(alignment: .trailing) { - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - .padding(.trailing, 6) - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - } - default: - Image(systemName: icon) - .foregroundColor(statusColor) - } + let (image, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) + image.foregroundColor(statusColor) } if let (title, text) = status.statusInfo { @@ -560,6 +556,6 @@ func localTimestamp(_ date: Date) -> String { struct ChatItemInfoView_Previews: PreviewProvider { static var previews: some View { - ChatItemInfoView(ci: ChatItem.getSample(1, .directSnd, .now, "hello"), chatItemInfo: Binding.constant(nil)) + ChatItemInfoView(ci: ChatItem.getSample(1, .directSnd, .now, "hello"), userMemberId: Chat.sampleData.chatInfo.groupInfo?.membership.memberId, chatItemInfo: Binding.constant(nil)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 870fe30108..f5558bcd93 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -9,40 +9,71 @@ import SwiftUI import SimpleXChat +extension EnvironmentValues { + struct ShowTimestamp: EnvironmentKey { + static let defaultValue: Bool = true + } + + struct Revealed: EnvironmentKey { + static let defaultValue: Bool = true + } + + struct ContainerBackground: EnvironmentKey { + static let defaultValue: UIColor = .clear + } + + var showTimestamp: Bool { + get { self[ShowTimestamp.self] } + set { self[ShowTimestamp.self] = newValue } + } + + var revealed: Bool { + get { self[Revealed.self] } + set { self[Revealed.self] = newValue } + } + + var containerBackground: UIColor { + get { self[ContainerBackground.self] } + set { self[ContainerBackground.self] = newValue } + } +} + struct ChatItemView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool + @Environment(\.revealed) var revealed: Bool var chatItem: ChatItem + var scrollToItemId: (ChatItem.ID) -> Void var maxWidth: CGFloat = .infinity - @Binding var revealed: Bool @Binding var allowMenu: Bool init( chat: Chat, chatItem: ChatItem, + scrollToItemId: @escaping (ChatItem.ID) -> Void, showMember: Bool = false, maxWidth: CGFloat = .infinity, - revealed: Binding, allowMenu: Binding = .constant(false) ) { self.chat = chat self.chatItem = chatItem + self.scrollToItemId = scrollToItemId self.maxWidth = maxWidth - _revealed = revealed _allowMenu = allowMenu } var body: some View { let ci = chatItem if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) { - MarkedDeletedItemView(chat: chat, chatItem: chatItem, revealed: $revealed) + MarkedDeletedItemView(chat: chat, chatItem: chatItem) } else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive { if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) { EmojiItemView(chat: chat, chatItem: ci) } else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent { CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu) } else if ci.content.msgContent == nil { - ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case + ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case } else { framedItemView() } @@ -60,7 +91,7 @@ struct ChatItemView: View { default: nil } } - .flatMap { UIImage(base64Encoded: $0) } + .flatMap { imageFromBase64($0) } let adjustedMaxWidth = { if let preview, preview.size.width <= preview.size.height { maxWidth * 0.75 @@ -71,8 +102,8 @@ struct ChatItemView: View { return FramedItemView( chat: chat, chatItem: chatItem, + scrollToItemId: scrollToItemId, preview: preview, - revealed: $revealed, maxWidth: maxWidth, imgWidth: adjustedMaxWidth, videoWidth: adjustedMaxWidth, @@ -84,9 +115,9 @@ struct ChatItemView: View { struct ChatItemContentView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.revealed) var revealed: Bool @ObservedObject var chat: Chat var chatItem: ChatItem - @Binding var revealed: Bool var msgContentView: () -> Content @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @@ -118,7 +149,7 @@ struct ChatItemContentView: View { case let .rcvChatPreference(feature, allowed, param): CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param) case let .sndChatPreference(feature, _, _): - CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary) + CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary) case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary)) case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary)) case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red) @@ -152,7 +183,7 @@ struct ChatItemContentView: View { private func eventItemViewText(_ secondaryColor: Color) -> Text { if !revealed, let t = mergedGroupEventText { - return chatEventText(t + Text(" ") + chatItem.timestampText, secondaryColor) + return chatEventText(t + textSpace + chatItem.timestampText, secondaryColor) } else if let member = chatItem.memberDisplayName { return Text(member + " ") .font(.caption) @@ -165,7 +196,7 @@ struct ChatItemContentView: View { } private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View { - CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor) + CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, iconColor: iconColor) } private var mergedGroupEventText: Text? { @@ -185,7 +216,7 @@ struct ChatItemContentView: View { } else if ns.count == 0 { Text("\(count) group events") } else if count > ns.count { - Text(members) + Text(" ") + Text("and \(count - ns.count) other events") + Text(members) + textSpace + Text("and \(count - ns.count) other events") } else { Text(members) } @@ -216,7 +247,7 @@ func chatEventText(_ text: Text, _ secondaryColor: Color) -> Text { } func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text, _ secondaryColor: Color) -> Text { - chatEventText(Text(eventText) + Text(" ") + ts, secondaryColor) + chatEventText(Text(eventText) + textSpace + ts, secondaryColor) } func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text { @@ -226,16 +257,17 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text { struct ChatItemView_Previews: PreviewProvider { static var previews: some View { Group{ - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), revealed: Binding.constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 70)) .environmentObject(Chat.sampleData) } @@ -254,7 +286,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - revealed: Binding.constant(true) + scrollToItemId: { _ in } ) ChatItemView( chat: Chat.sampleData, @@ -265,7 +297,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - revealed: Binding.constant(true) + scrollToItemId: { _ in } ) ChatItemView( chat: Chat.sampleData, @@ -276,7 +308,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - revealed: Binding.constant(true) + scrollToItemId: { _ in } ) ChatItemView( chat: Chat.sampleData, @@ -287,7 +319,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - revealed: Binding.constant(true) + scrollToItemId: { _ in } ) ChatItemView( chat: Chat.sampleData, @@ -298,9 +330,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - revealed: Binding.constant(true) + scrollToItemId: { _ in } ) } + .environment(\.revealed, true) .previewLayout(.fixed(width: 360, height: 70)) .environmentObject(Chat.sampleData) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift new file mode 100644 index 0000000000..07034cf8ec --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift @@ -0,0 +1,511 @@ +// +// ChatItemsLoader.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 17.12.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SimpleXChat +import SwiftUI + +let TRIM_KEEP_COUNT = 200 + +func apiLoadMessages( + _ chatId: ChatId, + _ pagination: ChatPagination, + _ chatState: ActiveChatState, + _ search: String = "", + _ openAroundItemId: ChatItem.ID? = nil, + _ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange = { 0 ... 0 } +) async { + let chat: Chat + let navInfo: NavigationInfo + do { + (chat, navInfo) = try await apiGetChat(chatId: chatId, pagination: pagination, search: search) + } catch let error { + logger.error("apiLoadMessages error: \(responseError(error))") + return + } + + let chatModel = ChatModel.shared + + // For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes + let paginationIsInitial = switch pagination { case .initial: true; default: false } + let paginationIsLast = switch pagination { case .last: true; default: false } + // When openAroundItemId is provided, chatId can be different too + if ((chatModel.chatId != chat.id || chat.chatItems.isEmpty) && !paginationIsInitial && !paginationIsLast && openAroundItemId == nil) || Task.isCancelled { + return + } + + let unreadAfterItemId = chatState.unreadAfterItemId + + let oldItems = Array(ItemsModel.shared.reversedChatItems.reversed()) + var newItems: [ChatItem] = [] + switch pagination { + case .initial: + let newSplits: [Int64] = if !chat.chatItems.isEmpty && navInfo.afterTotal > 0 { [chat.chatItems.last!.id] } else { [] } + if chatModel.getChat(chat.id) == nil { + chatModel.addChat(chat) + } + await MainActor.run { + chatModel.chatItemStatuses.removeAll() + ItemsModel.shared.reversedChatItems = chat.chatItems.reversed() + chatModel.updateChatInfo(chat.chatInfo) + chatState.splits = newSplits + if !chat.chatItems.isEmpty { + chatState.unreadAfterItemId = chat.chatItems.last!.id + } + chatState.totalAfter = navInfo.afterTotal + chatState.unreadTotal = chat.chatStats.unreadCount + chatState.unreadAfter = navInfo.afterUnread + chatState.unreadAfterNewestLoaded = navInfo.afterUnread + + PreloadState.shared.clear() + } + case let .before(paginationChatItemId, _): + newItems.append(contentsOf: oldItems) + let indexInCurrentItems = oldItems.firstIndex(where: { $0.id == paginationChatItemId }) + guard let indexInCurrentItems else { return } + let (newIds, _) = mapItemsToIds(chat.chatItems) + let wasSize = newItems.count + let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() } + let modifiedSplits = removeDuplicatesAndModifySplitsOnBeforePagination( + unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexes + ) + let insertAt = max((indexInCurrentItems - (wasSize - newItems.count) + modifiedSplits.trimmedIds.count), 0) + newItems.insert(contentsOf: chat.chatItems, at: insertAt) + let newReversed: [ChatItem] = newItems.reversed() + await MainActor.run { + ItemsModel.shared.reversedChatItems = newReversed + chatState.splits = modifiedSplits.newSplits + chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems) + } + case let .after(paginationChatItemId, _): + newItems.append(contentsOf: oldItems) + let indexInCurrentItems = oldItems.firstIndex(where: { $0.id == paginationChatItemId }) + guard let indexInCurrentItems else { return } + + let mappedItems = mapItemsToIds(chat.chatItems) + let newIds = mappedItems.0 + let (newSplits, unreadInLoaded) = removeDuplicatesAndModifySplitsOnAfterPagination( + mappedItems.1, paginationChatItemId, &newItems, newIds, chat, chatState.splits + ) + let indexToAdd = min(indexInCurrentItems + 1, newItems.count) + let indexToAddIsLast = indexToAdd == newItems.count + newItems.insert(contentsOf: chat.chatItems, at: indexToAdd) + let new: [ChatItem] = newItems + let newReversed: [ChatItem] = newItems.reversed() + await MainActor.run { + ItemsModel.shared.reversedChatItems = newReversed + chatState.splits = newSplits + chatState.moveUnreadAfterItem(chatState.splits.first ?? new.last!.id, new) + // loading clear bottom area, updating number of unread items after the newest loaded item + if indexToAddIsLast { + chatState.unreadAfterNewestLoaded -= unreadInLoaded + } + } + case .around: + var newSplits: [Int64] + if openAroundItemId == nil { + newItems.append(contentsOf: oldItems) + newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed) + } else { + newSplits = [] + } + let (itemIndex, splitIndex) = indexToInsertAround(chat.chatInfo.chatType, chat.chatItems.last, to: newItems, Set(newSplits)) + //indexToInsertAroundTest() + newItems.insert(contentsOf: chat.chatItems, at: itemIndex) + newSplits.insert(chat.chatItems.last!.id, at: splitIndex) + let newReversed: [ChatItem] = newItems.reversed() + let orderedSplits = newSplits + await MainActor.run { + ItemsModel.shared.reversedChatItems = newReversed + chatState.splits = orderedSplits + chatState.unreadAfterItemId = chat.chatItems.last!.id + chatState.totalAfter = navInfo.afterTotal + chatState.unreadTotal = chat.chatStats.unreadCount + chatState.unreadAfter = navInfo.afterUnread + + if let openAroundItemId { + chatState.unreadAfterNewestLoaded = navInfo.afterUnread + ChatModel.shared.openAroundItemId = openAroundItemId + ChatModel.shared.chatId = chatId + } else { + // no need to set it, count will be wrong + // chatState.unreadAfterNewestLoaded = navInfo.afterUnread + } + PreloadState.shared.clear() + } + case .last: + newItems.append(contentsOf: oldItems) + let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, chatState.splits) + newItems.append(contentsOf: chat.chatItems) + let items = newItems + await MainActor.run { + ItemsModel.shared.reversedChatItems = items.reversed() + chatState.splits = newSplits + chatModel.updateChatInfo(chat.chatInfo) + chatState.unreadAfterNewestLoaded = 0 + } + } +} + + +private class ModifiedSplits { + let oldUnreadSplitIndex: Int + let newUnreadSplitIndex: Int + let trimmedIds: Set + let newSplits: [Int64] + + init(oldUnreadSplitIndex: Int, newUnreadSplitIndex: Int, trimmedIds: Set, newSplits: [Int64]) { + self.oldUnreadSplitIndex = oldUnreadSplitIndex + self.newUnreadSplitIndex = newUnreadSplitIndex + self.trimmedIds = trimmedIds + self.newSplits = newSplits + } +} + +private func removeDuplicatesAndModifySplitsOnBeforePagination( + _ unreadAfterItemId: Int64, + _ newItems: inout [ChatItem], + _ newIds: Set, + _ splits: [Int64], + _ visibleItemIndexes: ClosedRange +) -> ModifiedSplits { + var oldUnreadSplitIndex: Int = -1 + var newUnreadSplitIndex: Int = -1 + var lastSplitIndexTrimmed: Int? = nil + var allowedTrimming = true + var index = 0 + /** keep the newest [TRIM_KEEP_COUNT] items (bottom area) and oldest [TRIM_KEEP_COUNT] items, trim others */ + let trimLowerBound = visibleItemIndexes.upperBound + TRIM_KEEP_COUNT + let trimUpperBound = newItems.count - TRIM_KEEP_COUNT + let trimRange = trimUpperBound >= trimLowerBound ? trimLowerBound ... trimUpperBound : -1 ... -1 + var trimmedIds = Set() + let prevTrimLowerBound = visibleItemIndexes.upperBound + TRIM_KEEP_COUNT + 1 + let prevTrimUpperBound = newItems.count - TRIM_KEEP_COUNT + let prevItemTrimRange = prevTrimUpperBound >= prevTrimLowerBound ? prevTrimLowerBound ... prevTrimUpperBound : -1 ... -1 + var newSplits = splits + + newItems.removeAll(where: { + let invisibleItemToTrim = trimRange.contains(index) && allowedTrimming + let prevItemWasTrimmed = prevItemTrimRange.contains(index) && allowedTrimming + // may disable it after clearing the whole split range + if !splits.isEmpty && $0.id == splits.first { + // trim only in one split range + allowedTrimming = false + } + let indexInSplits = splits.firstIndex(of: $0.id) + if let indexInSplits { + lastSplitIndexTrimmed = indexInSplits + } + if invisibleItemToTrim { + if prevItemWasTrimmed { + trimmedIds.insert($0.id) + } else { + newUnreadSplitIndex = index + // prev item is not supposed to be trimmed, so exclude current one from trimming and set a split here instead. + // this allows to define splitRange of the oldest items and to start loading trimmed items when user scrolls in the opposite direction + if let lastSplitIndexTrimmed { + var new = newSplits + new[lastSplitIndexTrimmed] = $0.id + newSplits = new + } else { + newSplits = [$0.id] + newSplits + } + } + } + if unreadAfterItemId == $0.id { + oldUnreadSplitIndex = index + } + index += 1 + return (invisibleItemToTrim && prevItemWasTrimmed) || newIds.contains($0.id) + }) + // will remove any splits that now becomes obsolete because items were merged + newSplits = newSplits.filter { split in !newIds.contains(split) && !trimmedIds.contains(split) } + return ModifiedSplits(oldUnreadSplitIndex: oldUnreadSplitIndex, newUnreadSplitIndex: newUnreadSplitIndex, trimmedIds: trimmedIds, newSplits: newSplits) +} + +private func removeDuplicatesAndModifySplitsOnAfterPagination( + _ unreadInLoaded: Int, + _ paginationChatItemId: Int64, + _ newItems: inout [ChatItem], + _ newIds: Set, + _ chat: Chat, + _ splits: [Int64] +) -> ([Int64], Int) { + var unreadInLoaded = unreadInLoaded + var firstItemIdBelowAllSplits: Int64? = nil + var splitsToRemove: Set = [] + let indexInSplitRanges = splits.firstIndex(of: paginationChatItemId) + // Currently, it should always load from split range + let loadingFromSplitRange = indexInSplitRanges != nil + let topSplits: [Int64] + var splitsToMerge: [Int64] + if let indexInSplitRanges, loadingFromSplitRange && indexInSplitRanges + 1 <= splits.count { + splitsToMerge = Array(splits[indexInSplitRanges + 1 ..< splits.count]) + topSplits = Array(splits[0 ..< indexInSplitRanges + 1]) + } else { + splitsToMerge = [] + topSplits = [] + } + newItems.removeAll(where: { new in + let duplicate = newIds.contains(new.id) + if loadingFromSplitRange && duplicate { + if splitsToMerge.contains(new.id) { + splitsToMerge.removeAll(where: { $0 == new.id }) + splitsToRemove.insert(new.id) + } else if firstItemIdBelowAllSplits == nil && splitsToMerge.isEmpty { + // we passed all splits and found duplicated item below all of them, which means no splits anymore below the loaded items + firstItemIdBelowAllSplits = new.id + } + } + if duplicate && new.isRcvNew { + unreadInLoaded -= 1 + } + return duplicate + }) + var newSplits: [Int64] = [] + if firstItemIdBelowAllSplits != nil { + // no splits below anymore, all were merged with bottom items + newSplits = topSplits + } else { + if !splitsToRemove.isEmpty { + var new = splits + new.removeAll(where: { splitsToRemove.contains($0) }) + newSplits = new + } + let enlargedSplit = splits.firstIndex(of: paginationChatItemId) + if let enlargedSplit { + // move the split to the end of loaded items + var new = splits + new[enlargedSplit] = chat.chatItems.last!.id + newSplits = new + } + } + return (newSplits, unreadInLoaded) +} + +private func removeDuplicatesAndUpperSplits( + _ newItems: inout [ChatItem], + _ chat: Chat, + _ splits: [Int64], + _ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange +) async -> [Int64] { + if splits.isEmpty { + removeDuplicates(&newItems, chat) + return splits + } + + var newSplits = splits + let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() } + let (newIds, _) = mapItemsToIds(chat.chatItems) + var idsToTrim: [BoxedValue>] = [] + idsToTrim.append(BoxedValue(Set())) + var index = 0 + newItems.removeAll(where: { + let duplicate = newIds.contains($0.id) + if (!duplicate && visibleItemIndexes.lowerBound > index) { + idsToTrim.last?.boxedValue.insert($0.id) + } + if visibleItemIndexes.lowerBound > index, let firstIndex = newSplits.firstIndex(of: $0.id) { + newSplits.remove(at: firstIndex) + // closing previous range. All items in idsToTrim that ends with empty set should be deleted. + // Otherwise, the last set should be excluded from trimming because it is in currently visible split range + idsToTrim.append(BoxedValue(Set())) + } + + index += 1 + return duplicate + }) + if !idsToTrim.last!.boxedValue.isEmpty { + // it has some elements to trim from currently visible range which means the items shouldn't be trimmed + // Otherwise, the last set would be empty + idsToTrim.removeLast() + } + let allItemsToDelete = idsToTrim.compactMap { set in set.boxedValue }.joined() + if !allItemsToDelete.isEmpty { + newItems.removeAll(where: { allItemsToDelete.contains($0.id) }) + } + return newSplits +} + +private func removeDuplicatesAndUnusedSplits( + _ newItems: inout [ChatItem], + _ chat: Chat, + _ splits: [Int64] +) async -> [Int64] { + if splits.isEmpty { + removeDuplicates(&newItems, chat) + return splits + } + + var newSplits = splits + let (newIds, _) = mapItemsToIds(chat.chatItems) + newItems.removeAll(where: { + let duplicate = newIds.contains($0.id) + if duplicate, let firstIndex = newSplits.firstIndex(of: $0.id) { + newSplits.remove(at: firstIndex) + } + return duplicate + }) + return newSplits +} + +// ids, number of unread items +private func mapItemsToIds(_ items: [ChatItem]) -> (Set, Int) { + var unreadInLoaded = 0 + var ids: Set = Set() + var i = 0 + while i < items.count { + let item = items[i] + ids.insert(item.id) + if item.isRcvNew { + unreadInLoaded += 1 + } + i += 1 + } + return (ids, unreadInLoaded) +} + +private func removeDuplicates(_ newItems: inout [ChatItem], _ chat: Chat) { + let (newIds, _) = mapItemsToIds(chat.chatItems) + newItems.removeAll { newIds.contains($0.id) } +} + +private typealias SameTimeItem = (index: Int, item: ChatItem) + +// return (item index, split index) +private func indexToInsertAround(_ chatType: ChatType, _ lastNew: ChatItem?, to: [ChatItem], _ splits: Set) -> (Int, Int) { + guard to.count > 0, let lastNew = lastNew else { return (0, 0) } + // group sorting: item_ts, item_id + // everything else: created_at, item_id + let compareByTimeTs = chatType == .group + // in case several items have the same time as another item in the `to` array + var sameTime: [SameTimeItem] = [] + + // trying to find new split index for item looks difficult but allows to not use one more loop. + // The idea is to memorize how many splits were till any index (map number of splits until index) + // and use resulting itemIndex to decide new split index position. + // Because of the possibility to have many items with the same timestamp, it's possible to see `itemIndex < || == || > i`. + var splitsTillIndex: [Int] = [] + var splitsPerPrevIndex = 0 + + for i in 0 ..< to.count { + let item = to[i] + + splitsPerPrevIndex = splits.contains(item.id) ? splitsPerPrevIndex + 1 : splitsPerPrevIndex + splitsTillIndex.append(splitsPerPrevIndex) + + let itemIsNewer = (compareByTimeTs ? item.meta.itemTs > lastNew.meta.itemTs : item.meta.createdAt > lastNew.meta.createdAt) + if itemIsNewer || i + 1 == to.count { + if (compareByTimeTs ? lastNew.meta.itemTs == item.meta.itemTs : lastNew.meta.createdAt == item.meta.createdAt) { + sameTime.append((i, item)) + } + // time to stop the loop. Item is newer or it's the last item in `to` array, taking previous items and checking position inside them + let itemIndex: Int + if sameTime.count > 1, let first = sameTime.sorted(by: { prev, next in prev.item.meta.itemId < next.item.id }).first(where: { same in same.item.id > lastNew.id }) { + itemIndex = first.index + } else if sameTime.count == 1 { + itemIndex = sameTime[0].item.id > lastNew.id ? sameTime[0].index : sameTime[0].index + 1 + } else { + itemIndex = itemIsNewer ? i : i + 1 + } + let splitIndex = splitsTillIndex[min(itemIndex, splitsTillIndex.count - 1)] + let prevItemSplitIndex = itemIndex == 0 ? 0 : splitsTillIndex[min(itemIndex - 1, splitsTillIndex.count - 1)] + return (itemIndex, splitIndex == prevItemSplitIndex ? splitIndex : prevItemSplitIndex) + } + + if (compareByTimeTs ? lastNew.meta.itemTs == item.meta.itemTs : lastNew.meta.createdAt == item.meta.createdAt) { + sameTime.append(SameTimeItem(index: i, item: item)) + } else { + sameTime = [] + } + } + // shouldn't be here + return (to.count, splits.count) +} + +private func indexToInsertAroundTest() { + func assert(_ one: (Int, Int), _ two: (Int, Int)) { + if one != two { + logger.debug("\(String(describing: one)) != \(String(describing: two))") + fatalError() + } + } + + let itemsToInsert = [ChatItem.getSample(3, .groupSnd, Date.init(timeIntervalSince1970: 3), "")] + let items1 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(1, .groupSnd, Date.init(timeIntervalSince1970: 1), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 2), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items1, Set([1])), (3, 1)) + + let items2 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(1, .groupSnd, Date.init(timeIntervalSince1970: 1), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items2, Set([2])), (3, 1)) + + let items3 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(1, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items3, Set([1])), (3, 1)) + + let items4 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items4, Set([4])), (1, 0)) + + let items5 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items5, Set([2])), (2, 1)) + + let items6 = [ + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(6, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items6, Set([5])), (0, 0)) + + let items7 = [ + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(6, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, nil, to: items7, Set([6])), (0, 0)) + + let items8 = [ + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items8, Set([2])), (0, 0)) + + let items9 = [ + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items9, Set([5])), (1, 0)) + + let items10 = [ + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(6, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items10, Set([4])), (0, 0)) + + let items11: [ChatItem] = [] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items11, Set([])), (0, 0)) +} diff --git a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift new file mode 100644 index 0000000000..0a55ed48cc --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift @@ -0,0 +1,456 @@ +// +// ChatItemsMerger.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 02.12.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct MergedItems: Hashable, Equatable { + let items: [MergedItem] + let splits: [SplitRange] + // chat item id, index in list + let indexInParentItems: Dictionary + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.hashValue == rhs.hashValue + } + + func hash(into hasher: inout Hasher) { + hasher.combine("\(items.hashValue)") + } + + static func create(_ items: [ChatItem], _ revealedItems: Set, _ chatState: ActiveChatState) -> MergedItems { + if items.isEmpty { + return MergedItems(items: [], splits: [], indexInParentItems: [:]) + } + + let unreadCount = chatState.unreadTotal + + let unreadAfterItemId = chatState.unreadAfterItemId + let itemSplits = chatState.splits + var mergedItems: [MergedItem] = [] + // Indexes of splits here will be related to reversedChatItems, not chatModel.chatItems + var splitRanges: [SplitRange] = [] + var indexInParentItems = Dictionary() + var index = 0 + var unclosedSplitIndex: Int? = nil + var unclosedSplitIndexInParent: Int? = nil + var visibleItemIndexInParent = -1 + var unreadBefore = unreadCount - chatState.unreadAfterNewestLoaded + var lastRevealedIdsInMergedItems: BoxedValue<[Int64]>? = nil + var lastRangeInReversedForMergedItems: BoxedValue>? = nil + var recent: MergedItem? = nil + while index < items.count { + let item = items[index] + let prev = index >= 1 ? items[index - 1] : nil + let next = index + 1 < items.count ? items[index + 1] : nil + let category = item.mergeCategory + let itemIsSplit = itemSplits.contains(item.id) + + if item.id == unreadAfterItemId { + unreadBefore = unreadCount - chatState.unreadAfter + } + if item.isRcvNew { + unreadBefore -= 1 + } + + let revealed = item.mergeCategory == nil || revealedItems.contains(item.id) + if recent != nil, case let .grouped(items, _, _, _, mergeCategory, unreadIds, _, _) = recent, mergeCategory == category, let first = items.boxedValue.first, !revealedItems.contains(first.item.id) && !itemIsSplit { + let listItem = ListItem(item: item, prevItem: prev, nextItem: next, unreadBefore: unreadBefore) + items.boxedValue.append(listItem) + + if item.isRcvNew { + unreadIds.boxedValue.insert(item.id) + } + if let lastRevealedIdsInMergedItems, let lastRangeInReversedForMergedItems { + if revealed { + lastRevealedIdsInMergedItems.boxedValue.append(item.id) + } + lastRangeInReversedForMergedItems.boxedValue = lastRangeInReversedForMergedItems.boxedValue.lowerBound ... index + } + } else { + visibleItemIndexInParent += 1 + let listItem = ListItem(item: item, prevItem: prev, nextItem: next, unreadBefore: unreadBefore) + if item.mergeCategory != nil { + if item.mergeCategory != prev?.mergeCategory || lastRevealedIdsInMergedItems == nil { + lastRevealedIdsInMergedItems = BoxedValue(revealedItems.contains(item.id) ? [item.id] : []) + } else if revealed, let lastRevealedIdsInMergedItems { + lastRevealedIdsInMergedItems.boxedValue.append(item.id) + } + lastRangeInReversedForMergedItems = BoxedValue(index ... index) + recent = MergedItem.grouped( + items: BoxedValue([listItem]), + revealed: revealed, + revealedIdsWithinGroup: lastRevealedIdsInMergedItems!, + rangeInReversed: lastRangeInReversedForMergedItems!, + mergeCategory: item.mergeCategory, + unreadIds: BoxedValue(item.isRcvNew ? Set(arrayLiteral: item.id) : Set()), + startIndexInReversedItems: index, + hash: listItem.genHash(revealedItems.contains(prev?.id ?? -1), revealedItems.contains(next?.id ?? -1)) + ) + } else { + lastRangeInReversedForMergedItems = nil + recent = MergedItem.single( + item: listItem, + startIndexInReversedItems: index, + hash: listItem.genHash(revealedItems.contains(prev?.id ?? -1), revealedItems.contains(next?.id ?? -1)) + ) + } + mergedItems.append(recent!) + } + if itemIsSplit { + // found item that is considered as a split + if let unclosedSplitIndex, let unclosedSplitIndexInParent { + // it was at least second split in the list + splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1)) + } + unclosedSplitIndex = index + unclosedSplitIndexInParent = visibleItemIndexInParent + } else if index + 1 == items.count, let unclosedSplitIndex, let unclosedSplitIndexInParent { + // just one split for the whole list, there will be no more, it's the end + splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent)) + } + indexInParentItems[item.id] = visibleItemIndexInParent + index += 1 + } + return MergedItems( + items: mergedItems, + splits: splitRanges, + indexInParentItems: indexInParentItems + ) + } + + // Use this check to ensure that mergedItems state based on currently actual state of global + // splits and reversedChatItems + func isActualState() -> Bool { + let im = ItemsModel.shared + // do not load anything if global splits state is different than in merged items because it + // will produce undefined results in terms of loading and placement of items. + // Same applies to reversedChatItems + return indexInParentItems.count == im.reversedChatItems.count && + splits.count == im.chatState.splits.count && + // that's just an optimization because most of the time only 1 split exists + ((splits.count == 1 && splits[0].itemId == im.chatState.splits[0]) || splits.map({ split in split.itemId }).sorted() == im.chatState.splits.sorted()) + } +} + + +enum MergedItem: Identifiable, Hashable, Equatable { + // equatable and hashable implementations allows to see the difference and correctly scroll to items we want + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.hash == rhs.hash + } + + var id: Int64 { newest().item.id } + + func hash(into hasher: inout Hasher) { + hasher.combine(hash) + } + + var hash: String { + switch self { + case .single(_, _, let hash): hash + " 1" + case .grouped(let items, _, _, _, _, _, _, let hash): hash + " \(items.boxedValue.count)" + } + } + + // the item that is always single, cannot be grouped and always revealed + case single( + item: ListItem, + startIndexInReversedItems: Int, + hash: String + ) + + /** The item that can contain multiple items or just one depending on revealed state. When the whole group of merged items is revealed, + * there will be multiple [Grouped] items with revealed flag set to true. When the whole group is collapsed, it will be just one instance + * of [Grouped] item with all grouped items inside [items]. In other words, number of [MergedItem] will always be equal to number of + * visible items in ChatView's EndlessScrollView */ + case grouped ( + items: BoxedValue<[ListItem]>, + revealed: Bool, + // it stores ids for all consecutive revealed items from the same group in order to hide them all on user's action + // it's the same list instance for all Grouped items within revealed group + /** @see reveal */ + revealedIdsWithinGroup: BoxedValue<[Int64]>, + rangeInReversed: BoxedValue>, + mergeCategory: CIMergeCategory?, + unreadIds: BoxedValue>, + startIndexInReversedItems: Int, + hash: String + ) + + func revealItems(_ reveal: Bool, _ revealedItems: Binding>) { + if case .grouped(let items, _, let revealedIdsWithinGroup, _, _, _, _, _) = self { + var newRevealed = revealedItems.wrappedValue + var i = 0 + if reveal { + while i < items.boxedValue.count { + newRevealed.insert(items.boxedValue[i].item.id) + i += 1 + } + } else { + while i < revealedIdsWithinGroup.boxedValue.count { + newRevealed.remove(revealedIdsWithinGroup.boxedValue[i]) + i += 1 + } + revealedIdsWithinGroup.boxedValue.removeAll() + } + revealedItems.wrappedValue = newRevealed + } + } + + var startIndexInReversedItems: Int { + get { + switch self { + case let .single(_, startIndexInReversedItems, _): startIndexInReversedItems + case let .grouped(_, _, _, _, _, _, startIndexInReversedItems, _): startIndexInReversedItems + } + } + } + + func hasUnread() -> Bool { + switch self { + case let .single(item, _, _): item.item.isRcvNew + case let .grouped(_, _, _, _, _, unreadIds, _, _): !unreadIds.boxedValue.isEmpty + } + } + + func newest() -> ListItem { + switch self { + case let .single(item, _, _): item + case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue[0] + } + } + + func oldest() -> ListItem { + switch self { + case let .single(item, _, _): item + case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue[items.boxedValue.count - 1] + } + } + + func lastIndexInReversed() -> Int { + switch self { + case .single: startIndexInReversedItems + case let .grouped(items, _, _, _, _, _, _, _): startIndexInReversedItems + items.boxedValue.count - 1 + } + } +} + +struct SplitRange { + let itemId: Int64 + /** range of indexes inside reversedChatItems where the first element is the split (it's index is [indexRangeInReversed.first]) + * so [0, 1, 2, -100-, 101] if the 3 is a split, SplitRange(indexRange = 3 .. 4) will be this SplitRange instance + * (3, 4 indexes of the splitRange with the split itself at index 3) + * */ + let indexRangeInReversed: ClosedRange + /** range of indexes inside LazyColumn where the first element is the split (it's index is [indexRangeInParentItems.first]) */ + let indexRangeInParentItems: ClosedRange +} + +struct ListItem: Hashable { + let item: ChatItem + let prevItem: ChatItem? + let nextItem: ChatItem? + // how many unread items before (older than) this one (excluding this one) + let unreadBefore: Int + + private func chatDirHash(_ chatDir: CIDirection?) -> Int { + guard let chatDir else { return 0 } + return switch chatDir { + case .directSnd: 0 + case .directRcv: 1 + case .groupSnd: 2 + case let .groupRcv(mem): "\(mem.groupMemberId) \(mem.displayName) \(mem.memberStatus.rawValue) \(mem.memberRole.rawValue) \(mem.image?.hash ?? 0)".hash + case .localSnd: 4 + case .localRcv: 5 + } + } + + // using meta.hashValue instead of parts takes much more time so better to use partial meta here + func genHash(_ prevRevealed: Bool, _ nextRevealed: Bool) -> String { + "\(item.meta.itemId) \(item.meta.updatedAt.hashValue) \(item.meta.itemEdited) \(item.meta.itemDeleted?.hashValue ?? 0) \(item.meta.itemTimed?.hashValue ?? 0) \(item.meta.itemStatus.hashValue) \(item.meta.sentViaProxy ?? false) \(item.mergeCategory?.hashValue ?? 0) \(chatDirHash(item.chatDir)) \(item.reactions.hashValue) \(item.meta.isRcvNew) \(item.text.hash) \(item.file?.hashValue ?? 0) \(item.quotedItem?.itemId ?? 0) \(unreadBefore) \(prevItem?.id ?? 0) \(chatDirHash(prevItem?.chatDir)) \(prevItem?.mergeCategory?.hashValue ?? 0) \(prevRevealed) \(nextItem?.id ?? 0) \(chatDirHash(nextItem?.chatDir)) \(nextItem?.mergeCategory?.hashValue ?? 0) \(nextRevealed)" + } +} + +class ActiveChatState { + var splits: [Int64] = [] + var unreadAfterItemId: Int64 = -1 + // total items after unread after item (exclusive) + var totalAfter: Int = 0 + var unreadTotal: Int = 0 + // exclusive + var unreadAfter: Int = 0 + // exclusive + var unreadAfterNewestLoaded: Int = 0 + + func moveUnreadAfterItem(_ toItemId: Int64?, _ nonReversedItems: [ChatItem]) { + guard let toItemId else { return } + let currentIndex = nonReversedItems.firstIndex(where: { $0.id == unreadAfterItemId }) + let newIndex = nonReversedItems.firstIndex(where: { $0.id == toItemId }) + guard let currentIndex, let newIndex else { + return + } + unreadAfterItemId = toItemId + let unreadDiff = newIndex > currentIndex + ? -nonReversedItems[currentIndex + 1.. fromIndex + ? -nonReversedItems[fromIndex + 1..?, _ newItems: [ChatItem]) { + guard let itemIds else { + // special case when the whole chat became read + unreadTotal = 0 + unreadAfter = 0 + return + } + var unreadAfterItemIndex: Int = -1 + // since it's more often that the newest items become read, it's logical to loop from the end of the list to finish it faster + var i = newItems.count - 1 + var ids = itemIds + // intermediate variables to prevent re-setting state value a lot of times without reason + var newUnreadTotal = unreadTotal + var newUnreadAfter = unreadAfter + while i >= 0 { + let item = newItems[i] + if item.id == unreadAfterItemId { + unreadAfterItemIndex = i + } + if ids.contains(item.id) { + // was unread, now this item is read + if (unreadAfterItemIndex == -1) { + newUnreadAfter -= 1 + } + newUnreadTotal -= 1 + ids.remove(item.id) + if ids.isEmpty { + break + } + } + i -= 1 + } + unreadTotal = newUnreadTotal + unreadAfter = newUnreadAfter + } + + func itemAdded(_ item: (Int64, Bool), _ index: Int) { + if item.1 { + unreadAfter += 1 + unreadTotal += 1 + } + } + + func itemsRemoved(_ itemIds: [(Int64, Int, Bool)], _ newItems: [ChatItem]) { + var newSplits: [Int64] = [] + for split in splits { + let index = itemIds.firstIndex(where: { (delId, _, _) in delId == split }) + // deleted the item that was right before the split between items, find newer item so it will act like the split + if let index { + let idx = itemIds[index].1 - itemIds.filter { (_, delIndex, _) in delIndex <= index }.count + let newSplit = newItems.count > idx && idx >= 0 ? newItems[idx].id : nil + // it the whole section is gone and splits overlap, don't add it at all + if let newSplit, !newSplits.contains(newSplit) { + newSplits.append(newSplit) + } + } else { + newSplits.append(split) + } + } + splits = newSplits + + let index = itemIds.firstIndex(where: { (delId, _, _) in delId == unreadAfterItemId }) + // unread after item was removed + if let index { + let idx = itemIds[index].1 - itemIds.filter { (_, delIndex, _) in delIndex <= index }.count + var newUnreadAfterItemId = newItems.count > idx && idx >= 0 ? newItems[idx].id : nil + let newUnreadAfterItemWasNull = newUnreadAfterItemId == nil + if newUnreadAfterItemId == nil { + // everything on top (including unread after item) were deleted, take top item as unread after id + newUnreadAfterItemId = newItems.first?.id + } + if let newUnreadAfterItemId { + unreadAfterItemId = newUnreadAfterItemId + totalAfter -= itemIds.filter { (_, delIndex, _) in delIndex > index }.count + unreadTotal -= itemIds.filter { (_, delIndex, isRcvNew) in delIndex <= index && isRcvNew }.count + unreadAfter -= itemIds.filter { (_, delIndex, isRcvNew) in delIndex > index && isRcvNew }.count + if newUnreadAfterItemWasNull { + // since the unread after item was moved one item after initial position, adjust counters accordingly + if newItems.first?.isRcvNew == true { + unreadTotal += 1 + unreadAfter -= 1 + } + } + } else { + // all items were deleted, 0 items in chatItems + unreadAfterItemId = -1 + totalAfter = 0 + unreadTotal = 0 + unreadAfter = 0 + } + } else { + totalAfter -= itemIds.count + } + } +} + +class BoxedValue: Equatable, Hashable { + static func == (lhs: BoxedValue, rhs: BoxedValue) -> Bool { + lhs.boxedValue == rhs.boxedValue + } + + func hash(into hasher: inout Hasher) { + hasher.combine("\(self)") + } + + var boxedValue : T + init(_ value: T) { + self.boxedValue = value + } +} + +@MainActor +func visibleItemIndexesNonReversed(_ listState: EndlessScrollView.ListState, _ mergedItems: MergedItems) -> ClosedRange { + let zero = 0 ... 0 + let items = mergedItems.items + if items.isEmpty { + return zero + } + let newest = items.count > listState.firstVisibleItemIndex ? items[listState.firstVisibleItemIndex].startIndexInReversedItems : nil + let oldest = items.count > listState.lastVisibleItemIndex ? items[listState.lastVisibleItemIndex].lastIndexInReversed() : nil + guard let newest, let oldest else { + return zero + } + let size = ItemsModel.shared.reversedChatItems.count + let range = size - oldest ... size - newest + if range.lowerBound < 0 || range.upperBound < 0 { + return zero + } + + // visible items mapped to their underlying data structure which is ItemsModel.shared.reversedChatItems.reversed() + return range +} diff --git a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift new file mode 100644 index 0000000000..c1a1eec7d2 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift @@ -0,0 +1,185 @@ +// +// ChatScrollHelpers.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 20.12.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat) async { + await MainActor.run { + loadingMoreItems.wrappedValue = true + loadingBottomItems.wrappedValue = true + } + try? await Task.sleep(nanoseconds: 500_000000) + if ChatModel.shared.chatId != chat.chatInfo.id { + await MainActor.run { + loadingMoreItems.wrappedValue = false + loadingBottomItems.wrappedValue = false + } + return + } + await apiLoadMessages(chat.chatInfo.id, ChatPagination.last(count: 50), ItemsModel.shared.chatState) + await MainActor.run { + loadingMoreItems.wrappedValue = false + loadingBottomItems.wrappedValue = false + } +} + +class PreloadState { + static let shared = PreloadState() + var prevFirstVisible: Int64 = Int64.min + var prevItemsCount: Int = 0 + var preloading: Bool = false + + func clear() { + prevFirstVisible = Int64.min + prevItemsCount = 0 + preloading = false + } +} + +func preloadIfNeeded( + _ allowLoadMoreItems: Binding, + _ ignoreLoadingRequests: Binding, + _ listState: EndlessScrollView.ListState, + _ mergedItems: BoxedValue, + loadItems: @escaping (Bool, ChatPagination) async -> Bool, + loadLastItems: @escaping () async -> Void +) { + let state = PreloadState.shared + guard !listState.isScrolling && !listState.isAnimatedScrolling, + !state.preloading, + listState.totalItemsCount > 0 + else { + return + } + if state.prevFirstVisible != listState.firstVisibleItemId as! Int64 || state.prevItemsCount != mergedItems.boxedValue.indexInParentItems.count { + state.preloading = true + let allowLoadMore = allowLoadMoreItems.wrappedValue + Task { + defer { state.preloading = false } + var triedToLoad = true + await preloadItems(mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in + triedToLoad = await loadItems(false, pagination) + return triedToLoad + } + if triedToLoad { + state.prevFirstVisible = listState.firstVisibleItemId as! Int64 + state.prevItemsCount = mergedItems.boxedValue.indexInParentItems.count + } + // it's important to ask last items when the view is fully covered with items. Otherwise, visible items from one + // split will be merged with last items and position of scroll will change unexpectedly. + if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded { + await loadLastItems() + } + } + } else if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded { + state.preloading = true + Task { + defer { state.preloading = false } + await loadLastItems() + } + } +} + +func preloadItems( + _ mergedItems: MergedItems, + _ allowLoadMoreItems: Bool, + _ listState: EndlessScrollView.ListState, + _ ignoreLoadingRequests: Binding, + _ loadItems: @escaping (ChatPagination) async -> Bool) +async { + let allowLoad = allowLoadMoreItems || mergedItems.items.count == listState.lastVisibleItemIndex + 1 + let remaining = ChatPagination.UNTIL_PRELOAD_COUNT + let firstVisibleIndex = listState.firstVisibleItemIndex + + if !(await preloadItemsBefore()) { + await preloadItemsAfter() + } + + func preloadItemsBefore() async -> Bool { + let splits = mergedItems.splits + let lastVisibleIndex = listState.lastVisibleItemIndex + var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits) + let items: [ChatItem] = ItemsModel.shared.reversedChatItems.reversed() + if splits.isEmpty && !items.isEmpty && lastVisibleIndex > mergedItems.items.count - remaining { + lastIndexToLoadFrom = items.count - 1 + } + let loadFromItemId: Int64? + if allowLoad, let lastIndexToLoadFrom { + let index = items.count - 1 - lastIndexToLoadFrom + loadFromItemId = index >= 0 ? items[index].id : nil + } else { + loadFromItemId = nil + } + guard let loadFromItemId, ignoreLoadingRequests.wrappedValue != loadFromItemId else { + return false + } + let sizeWas = items.count + let firstItemIdWas = items.first?.id + let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT)) + if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id { + ignoreLoadingRequests.wrappedValue = loadFromItemId + return false + } + return triedToLoad + } + + func preloadItemsAfter() async { + let splits = mergedItems.splits + let split = splits.last(where: { $0.indexRangeInParentItems.contains(firstVisibleIndex) }) + // we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom) + let reversedItems: [ChatItem] = ItemsModel.shared.reversedChatItems + if let split, split.indexRangeInParentItems.lowerBound + remaining > firstVisibleIndex { + let index = split.indexRangeInReversed.lowerBound + if index >= 0 { + let loadFromItemId = reversedItems[index].id + _ = await loadItems(ChatPagination.after(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT)) + } + } + } +} + +func oldestPartiallyVisibleListItemInListStateOrNull(_ listState: EndlessScrollView.ListState) -> ListItem? { + if listState.lastVisibleItemIndex < listState.items.count { + return listState.items[listState.lastVisibleItemIndex].oldest() + } else { + return listState.items.last?.oldest() + } +} + +private func findLastIndexToLoadFromInSplits(_ firstVisibleIndex: Int, _ lastVisibleIndex: Int, _ remaining: Int, _ splits: [SplitRange]) -> Int? { + for split in splits { + // before any split + if split.indexRangeInParentItems.lowerBound > firstVisibleIndex { + if lastVisibleIndex > (split.indexRangeInParentItems.lowerBound - remaining) { + return split.indexRangeInReversed.lowerBound - 1 + } + break + } + let containsInRange = split.indexRangeInParentItems.contains(firstVisibleIndex) + if containsInRange { + if lastVisibleIndex > (split.indexRangeInParentItems.upperBound - remaining) { + return split.indexRangeInReversed.upperBound + } + break + } + } + return nil +} + +/// Disable animation on iOS 15 +func withConditionalAnimation( + _ animation: Animation? = .default, + _ body: () throws -> Result +) rethrows -> Result { + if #available(iOS 16.0, *) { + try withAnimation(animation, body) + } else { + try body() + } +} diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 655dd8aaed..c136ebc01b 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -16,36 +16,48 @@ private let memberImageSize: CGFloat = 34 struct ChatView: View { @EnvironmentObject var chatModel: ChatModel @ObservedObject var im = ItemsModel.shared + @State var mergedItems: BoxedValue = BoxedValue(MergedItems.create(ItemsModel.shared.reversedChatItems, [], ItemsModel.shared.chatState)) + @State var revealedItems: Set = Set() @State var theme: AppTheme = buildTheme() @Environment(\.dismiss) var dismiss @Environment(\.colorScheme) var colorScheme @Environment(\.presentationMode) var presentationMode @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat - @StateObject private var scrollModel = ReverseListScrollModel() - @StateObject private var floatingButtonModel = FloatingButtonModel() @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() + @State private var selectedRange = NSRange() @State private var keyboardVisible = false + @State private var keyboardHiddenDate = Date.now @State private var connectionStats: ConnectionStats? @State private var customUserProfile: Profile? @State private var connectionCode: String? - @State private var loadingItems = false - @State private var firstPage = false - @State private var revealedChatItem: ChatItem? - @State private var searchMode = false + @State private var loadingMoreItems = false + @State private var loadingTopItems = false + @State private var requestedTopScroll = false + @State private var loadingBottomItems = false + @State private var requestedBottomScroll = false + @State private var showSearch = false @State private var searchText: String = "" @FocusState private var searchFocussed // opening GroupMemberInfoView on member icon @State private var selectedMember: GMember? = nil // opening GroupLinkView on link button (incognito) @State private var showGroupLinkSheet: Bool = false - @State private var groupLink: String? + @State private var groupLink: CreatedConnLink? @State private var groupLinkMemberRole: GroupMemberRole = .member + @State private var forwardedChatItems: [ChatItem] = [] @State private var selectedChatItems: Set? = nil @State private var showDeleteSelectedMessages: Bool = false + @State private var showArchiveSelectedReports: Bool = false @State private var allowToDeleteSelectedMessagesForAll: Bool = false + @State private var allowLoadMoreItems: Bool = false + @State private var ignoreLoadingRequests: Int64? = nil + @State private var animatedScrollingInProgress: Bool = false + @State private var floatingButtonModel: FloatingButtonModel = FloatingButtonModel() + + @State private var scrollView: EndlessScrollView = EndlessScrollView(frame: .zero) @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @@ -59,10 +71,9 @@ struct ChatView: View { } } - @ViewBuilder private var viewBody: some View { let cInfo = chat.chatInfo - ZStack { + return ZStack { let wallpaperImage = theme.wallpaper.type.image let wallpaperType = theme.wallpaper.type let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background) @@ -76,16 +87,35 @@ struct ChatView: View { VStack(spacing: 0) { ZStack(alignment: .bottomTrailing) { chatItemsList() - floatingButtons(counts: floatingButtonModel.unreadChatItemCounts) + if let groupInfo = chat.chatInfo.groupInfo, !composeState.message.isEmpty { + GroupMentionsView(groupInfo: groupInfo, composeState: $composeState, selectedRange: $selectedRange, keyboardVisible: $keyboardVisible) + } + FloatingButtons(theme: theme, scrollView: scrollView, chat: chat, loadingMoreItems: $loadingMoreItems, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel, reloadItems: { + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + scrollView.updateItems(mergedItems.boxedValue.items) + } + ) } connectingText() if selectedChatItems == nil { + let reason = chat.chatInfo.userCantSendReason ComposeView( chat: chat, composeState: $composeState, - keyboardVisible: $keyboardVisible + keyboardVisible: $keyboardVisible, + keyboardHiddenDate: $keyboardHiddenDate, + selectedRange: $selectedRange, + disabledText: reason?.composeLabel ) .disabled(!cInfo.sendMsgEnabled) + .if(!cInfo.sendMsgEnabled) { v in + v.disabled(true).onTapGesture { + AlertManager.shared.showAlertMsg( + title: "You can't send messages!", + message: reason?.alertMessage + ) + } + } } else { SelectedItemsBottomToolbar( chatItems: ItemsModel.shared.reversedChatItems, @@ -95,18 +125,25 @@ struct ChatView: View { allowToDeleteSelectedMessagesForAll = forAll showDeleteSelectedMessages = true }, + archiveItems: { + showArchiveSelectedReports = true + }, moderateItems: { if case let .group(groupInfo) = chat.chatInfo { showModerateSelectedMessagesAlert(groupInfo) } - } + }, + forwardItems: forwardSelectedMessages ) } } + if im.showLoadingProgress == chat.id { + ProgressView().scaleEffect(2) + } } .safeAreaInset(edge: .top) { VStack(spacing: .zero) { - if searchMode { searchToolbar() } + if showSearch { searchToolbar() } Divider() } .background(ToolbarMaterial.material(toolbarMaterial)) @@ -129,43 +166,122 @@ struct ChatView: View { } } } - .appSheet(item: $selectedMember) { member in - Group { - if case let .group(groupInfo) = chat.chatInfo { - GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true) + .confirmationDialog(selectedChatItems?.count == 1 ? "Archive report?" : "Archive \((selectedChatItems?.count ?? 0)) reports?", isPresented: $showArchiveSelectedReports, titleVisibility: .visible) { + Button("For me", role: .destructive) { + if let selected = selectedChatItems { + archiveReports(chat.chatInfo, selected.sorted(), false, deletedSelectedMessages) + } + } + if case let ChatInfo.group(groupInfo) = chat.chatInfo, groupInfo.membership.memberActive { + Button("For all moderators", role: .destructive) { + if let selected = selectedChatItems { + archiveReports(chat.chatInfo, selected.sorted(), true, deletedSelectedMessages) + } } } } + .appSheet(item: $selectedMember) { member in + Group { + if case let .group(groupInfo) = chat.chatInfo { + GroupMemberInfoView( + groupInfo: groupInfo, + chat: chat, + groupMember: member, + navigation: true + ) + } + } + } + // it should be presented on top level in order to prevent a bug in SwiftUI on iOS 16 related to .focused() modifier in AddGroupMembersView's search field + .appSheet(isPresented: $showAddMembersSheet) { + Group { + if case let .group(groupInfo) = cInfo { + AddGroupMembersView(chat: chat, groupInfo: groupInfo) + } + } + } + .sheet(isPresented: Binding( + get: { !forwardedChatItems.isEmpty }, + set: { isPresented in + if !isPresented { + forwardedChatItems = [] + selectedChatItems = nil + } + } + )) { + if #available(iOS 16.0, *) { + ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) + .presentationDetents([.fraction(0.8)]) + } else { + ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) + } + } .onAppear { + scrollView.listState.onUpdateListener = onChatItemsUpdated selectedChatItems = nil + revealedItems = Set() initChatView() + if im.isLoading { + Task { + try? await Task.sleep(nanoseconds: 500_000000) + await MainActor.run { + if im.isLoading { + im.showLoadingProgress = chat.id + } + } + } + } } .onChange(of: chatModel.chatId) { cId in showChatInfoSheet = false selectedChatItems = nil - scrollModel.scrollToBottom() + revealedItems = Set() stopAudioPlayer() if let cId { if let c = chatModel.getChat(cId) { chat = c } + scrollView.listState.onUpdateListener = onChatItemsUpdated initChatView() theme = buildTheme() + closeSearch() + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + scrollView.updateItems(mergedItems.boxedValue.items) + + if let openAround = chatModel.openAroundItemId, let index = mergedItems.boxedValue.indexInParentItems[openAround] { + scrollView.scrollToItem(index) + } else if let unreadIndex = mergedItems.boxedValue.items.lastIndex(where: { $0.hasUnread() }) { + scrollView.scrollToItem(unreadIndex) + } else { + scrollView.scrollToBottom() + } + if chatModel.openAroundItemId != nil { + chatModel.openAroundItemId = nil + } } else { dismiss() } } - .onChange(of: revealedChatItem) { _ in - NotificationCenter.postReverseListNeedsLayout() - } - .onChange(of: im.isLoading) { isLoading in - if !isLoading, - im.reversedChatItems.count <= loadItemsPerPage, - filtered(im.reversedChatItems).count < 10 { - loadChatItems(chat.chatInfo) + .onChange(of: chatModel.openAroundItemId) { openAround in + if let openAround { + closeSearch() + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + scrollView.updateItems(mergedItems.boxedValue.items) + chatModel.openAroundItemId = nil + + if let index = mergedItems.boxedValue.indexInParentItems[openAround] { + scrollView.scrollToItem(index) + } + + // this may already being loading because of changed chat id (see .onChange(of: chat.id) + if !loadingBottomItems { + allowLoadMoreItems = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + allowLoadMoreItems = true + } + } } } - .environmentObject(scrollModel) .onDisappear { VideoPlayerView.players.removeAll() stopAudioPlayer() @@ -174,6 +290,7 @@ struct ChatView: View { if chatModel.chatId == nil { chatModel.chatItemStatuses = [:] ItemsModel.shared.reversedChatItems = [] + ItemsModel.shared.chatState.clear() chatModel.groupMembers = [] chatModel.groupMembersIndexes.removeAll() chatModel.membersLoaded = false @@ -201,6 +318,8 @@ struct ChatView: View { chat: chat, contact: contact, localAlias: chat.chatInfo.localAlias, + featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), + currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), onSearch: { focusSearch() } ) } @@ -221,7 +340,8 @@ struct ChatView: View { chat.created = Date.now } ), - onSearch: { focusSearch() } + onSearch: { focusSearch() }, + localAlias: groupInfo.localAlias ) } } else if case .local = cInfo { @@ -229,7 +349,6 @@ struct ChatView: View { } } ToolbarItem(placement: .navigationBarTrailing) { - let isLoading = im.isLoading && im.showLoadingProgress if selectedChatItems != nil { Button { withAnimation { @@ -252,23 +371,19 @@ struct ChatView: View { } } Menu { - if !isLoading { - if callsPrefEnabled && chatModel.activeCall == nil { - Button { - CallController.shared.startCall(contact, .video) - } label: { - Label("Video call", systemImage: "video") - } - .disabled(!contact.ready || !contact.active) + if callsPrefEnabled && chatModel.activeCall == nil { + Button { + CallController.shared.startCall(contact, .video) + } label: { + Label("Video call", systemImage: "video") } - searchButton() - ToggleNtfsButton(chat: chat) - .disabled(!contact.ready || !contact.active) + .disabled(!contact.ready || !contact.active) } + searchButton() + ToggleNtfsButton(chat: chat) + .disabled(!contact.ready || !contact.active) } label: { Image(systemName: "ellipsis") - .tint(isLoading ? Color.clear : nil) - .overlay { if isLoading { ProgressView() } } } } case let .group(groupInfo): @@ -287,20 +402,13 @@ struct ChatView: View { } } else { addMembersButton() - .appSheet(isPresented: $showAddMembersSheet) { - AddGroupMembersView(chat: chat, groupInfo: groupInfo) - } } } Menu { - if !isLoading { - searchButton() - ToggleNtfsButton(chat: chat) - } + searchButton() + ToggleNtfsButton(chat: chat) } label: { Image(systemName: "ellipsis") - .tint(isLoading ? Color.clear : nil) - .overlay { if isLoading { ProgressView() } } } } case .local: @@ -312,7 +420,7 @@ struct ChatView: View { } } } - + private func initChatView() { let cInfo = chat.chatInfo // This check prevents the call to apiContactInfo after the app is suspended, and the database is closed. @@ -340,6 +448,40 @@ struct ChatView: View { await markChatUnread(chat, unreadChat: false) } } + floatingButtonModel.updateOnListChange(scrollView.listState) + } + + private func scrollToItemId(_ itemId: ChatItem.ID) { + Task { + do { + var index = mergedItems.boxedValue.indexInParentItems[itemId] + if index == nil { + let pagination = ChatPagination.around(chatItemId: itemId, count: ChatPagination.PRELOAD_COUNT * 2) + let oldSize = ItemsModel.shared.reversedChatItems.count + let triedToLoad = await loadChatItems(chat, pagination) + if !triedToLoad { + return + } + var repeatsLeft = 50 + while oldSize == ItemsModel.shared.reversedChatItems.count && repeatsLeft > 0 { + try await Task.sleep(nanoseconds: 20_000000) + repeatsLeft -= 1 + } + index = mergedItems.boxedValue.indexInParentItems[itemId] + } + if let index { + closeKeyboardAndRun { + Task { + await MainActor.run { animatedScrollingInProgress = true } + await scrollView.scrollToItemAnimated(min(ItemsModel.shared.reversedChatItems.count - 1, index)) + await MainActor.run { animatedScrollingInProgress = false } + } + } + } + } catch { + logger.error("Error scrolling to item: \(error)") + } + } } private func searchToolbar() -> some View { @@ -363,16 +505,14 @@ struct ChatView: View { .cornerRadius(10.0) Button ("Cancel") { - searchText = "" - searchMode = false - searchFocussed = false - Task { await loadChat(chat: chat) } + closeSearch() + searchTextChanged("") } } .padding(.horizontal) .padding(.vertical, 8) } - + private func voiceWithoutFrame(_ ci: ChatItem) -> Bool { ci.content.msgContent?.isVoice == true && ci.content.text.count == 0 && ci.quotedItem == nil && ci.meta.itemForwarded == nil } @@ -390,56 +530,84 @@ struct ChatView: View { .map { $0.element } } - private func chatItemsList() -> some View { let cInfo = chat.chatInfo - let mergedItems = filtered(im.reversedChatItems) return GeometryReader { g in - ReverseList(items: mergedItems, scrollState: $scrollModel.state) { ci in + //let _ = logger.debug("Reloading chatItemsList with number of itmes: \(im.reversedChatItems.count)") + ScrollRepresentable(scrollView: scrollView) { (index: Int, mergedItem: MergedItem) in + let ci = switch mergedItem { + case let .single(item, _, _): item.item + case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue.last!.item + } let voiceNoFrame = voiceWithoutFrame(ci) let maxWidth = cInfo.chatType == .group - ? voiceNoFrame - ? (g.size.width - 28) - 42 - : (g.size.width - 28) * 0.84 - 42 - : voiceNoFrame - ? (g.size.width - 32) - : (g.size.width - 32) * 0.84 + ? voiceNoFrame + ? (g.size.width - 28) - 42 + : (g.size.width - 28) * 0.84 - 42 + : voiceNoFrame + ? (g.size.width - 32) + : (g.size.width - 32) * 0.84 return ChatItemWithMenu( chat: $chat, + index: index, + isLastItem: index == mergedItems.boxedValue.items.count - 1, chatItem: ci, + scrollToItemId: scrollToItemId, + merged: mergedItem, maxWidth: maxWidth, composeState: $composeState, selectedMember: $selectedMember, - revealedChatItem: $revealedChatItem, - selectedChatItems: $selectedChatItems + showChatInfoSheet: $showChatInfoSheet, + revealedItems: $revealedItems, + selectedChatItems: $selectedChatItems, + forwardedChatItems: $forwardedChatItems, + searchText: $searchText, + closeKeyboardAndRun: closeKeyboardAndRun ) - .onAppear { - floatingButtonModel.appeared(viewId: ci.viewId) - } - .onDisappear { - floatingButtonModel.disappeared(viewId: ci.viewId) - } + // crashes on Cell size calculation without this line + .environmentObject(ChatModel.shared) + .environmentObject(theme) // crashes without this line when scrolling to the first unread in EndlessScrollVIew .id(ci.id) // Required to trigger `onAppear` on iOS15 - } loadPage: { - loadChatItems(cInfo) } - .opacity(ItemsModel.shared.isLoading ? 0 : 1) - .padding(.vertical, -InvertedTableView.inset) - .onTapGesture { hideKeyboard() } - .onChange(of: searchText) { _ in - Task { await loadChat(chat: chat, search: searchText) } + .onAppear { + if !im.isLoading { + updateWithInitiallyLoadedItems() + } } - .onChange(of: im.reversedChatItems) { _ in - floatingButtonModel.chatItemsChanged() + .onChange(of: im.isLoading) { loading in + if !loading { + updateWithInitiallyLoadedItems() + } } - .onChange(of: im.itemAdded) { added in - if added { + .onChange(of: im.reversedChatItems) { items in + mergedItems.boxedValue = MergedItems.create(items, revealedItems, im.chatState) + scrollView.updateItems(mergedItems.boxedValue.items) + if im.itemAdded { im.itemAdded = false - if floatingButtonModel.unreadChatItemCounts.isReallyNearBottom { - scrollModel.scrollToBottom() + if scrollView.listState.firstVisibleItemIndex < 2 { + scrollView.scrollToBottomAnimated() + } else { + scrollView.scroll(by: 34) } } } + .onChange(of: revealedItems) { revealed in + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealed, im.chatState) + scrollView.updateItems(mergedItems.boxedValue.items) + } + .onChange(of: chat.id) { _ in + allowLoadMoreItems = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + allowLoadMoreItems = true + } + } + .padding(.vertical, -100) + .onTapGesture { hideKeyboard() } + .onChange(of: searchText) { s in + if showSearch { + searchTextChanged(s) + } + } } } @@ -457,104 +625,271 @@ struct ChatView: View { } } + private func updateWithInitiallyLoadedItems() { + if mergedItems.boxedValue.items.isEmpty { + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, ItemsModel.shared.chatState) + } + let unreadIndex = mergedItems.boxedValue.items.lastIndex(where: { $0.hasUnread() }) + let unreadItemId: Int64? = if let unreadIndex { mergedItems.boxedValue.items[unreadIndex].newest().item.id } else { nil } + // this helps to speed up initial process of setting scroll position and reduce time needed + // to layout items on screen + if let unreadIndex, let unreadItemId { + scrollView.setScrollPosition(unreadIndex, unreadItemId) + } + scrollView.updateItems(mergedItems.boxedValue.items) + if let unreadIndex { + scrollView.scrollToItem(unreadIndex) + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + allowLoadMoreItems = true + } + } + + private func searchTextChanged(_ s: String) { + Task { + await loadChat(chat: chat, search: s) + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + await MainActor.run { + scrollView.updateItems(mergedItems.boxedValue.items) + } + if !s.isEmpty { + scrollView.scrollToBottom() + } else if let index = scrollView.listState.items.lastIndex(where: { $0.hasUnread() }) { + // scroll to the top unread item + scrollView.scrollToItem(index) + } else { + scrollView.scrollToBottom() + } + } + } + class FloatingButtonModel: ObservableObject { - private enum Event { - case appeared(String) - case disappeared(String) - case chatItemsChanged - } + @Published var unreadAbove: Int = 0 + @Published var unreadBelow: Int = 0 + @Published var isNearBottom: Bool = true + @Published var date: Date? = nil + @Published var isDateVisible: Bool = false + var hideDateWorkItem: DispatchWorkItem? = nil - @Published var unreadChatItemCounts: UnreadChatItemCounts - - private let events = PassthroughSubject() - private var bag = Set() - - init() { - unreadChatItemCounts = UnreadChatItemCounts( - isNearBottom: true, - isReallyNearBottom: true, - unreadBelow: 0 - ) - events - .receive(on: DispatchQueue.global(qos: .background)) - .scan(Set()) { itemsInView, event in - var updated = itemsInView - switch event { - case let .appeared(viewId): updated.insert(viewId) - case let .disappeared(viewId): updated.remove(viewId) - case .chatItemsChanged: () - } - return updated + func updateOnListChange(_ listState: EndlessScrollView.ListState) { + let lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(listState) + let unreadBelow = if let lastVisibleItem { + max(0, ItemsModel.shared.chatState.unreadTotal - lastVisibleItem.unreadBefore) + } else { + 0 + } + let unreadAbove = ItemsModel.shared.chatState.unreadTotal - unreadBelow + let date: Date? = + if let lastVisible = listState.visibleItems.last { + Calendar.current.startOfDay(for: lastVisible.item.oldest().item.meta.itemTs) + } else { + nil } - .map { ChatModel.shared.unreadChatItemCounts(itemsInView: $0) } - .removeDuplicates() - .throttle(for: .seconds(0.2), scheduler: DispatchQueue.main, latest: true) - .assign(to: \.unreadChatItemCounts, on: self) - .store(in: &bag) + + // set the counters and date indicator + DispatchQueue.main.async { [weak self] in + guard let it = self else { return } + it.setDate(visibility: true) + it.unreadAbove = unreadAbove + it.unreadBelow = unreadBelow + it.date = date + } + + // set floating button indication mode + let nearBottom = listState.firstVisibleItemIndex < 1 + if nearBottom != self.isNearBottom { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in + self?.isNearBottom = nearBottom + } + } + + // hide Date indicator after 1 second of no scrolling + hideDateWorkItem?.cancel() + let workItem = DispatchWorkItem { [weak self] in + guard let it = self else { return } + it.setDate(visibility: false) + it.hideDateWorkItem = nil + } + DispatchQueue.main.async { [weak self] in + self?.hideDateWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem) + } } - func appeared(viewId: String) { - events.send(.appeared(viewId)) + func resetDate() { + date = nil + isDateVisible = false } - func disappeared(viewId: String) { - events.send(.disappeared(viewId)) + private func setDate(visibility isVisible: Bool) { + if isVisible { + if !isNearBottom, + !isDateVisible, + let date, !Calendar.current.isDateInToday(date) { + withAnimation { self.isDateVisible = true } + } + } else if isDateVisible { + withAnimation { self.isDateVisible = false } + } } - func chatItemsChanged() { - events.send(.chatItemsChanged) - } } - private func floatingButtons(counts: UnreadChatItemCounts) -> some View { - VStack { - let unreadAbove = chat.chatStats.unreadCount - counts.unreadBelow - if unreadAbove > 0 { - circleButton { - unreadCountText(unreadAbove) - .font(.callout) - .foregroundColor(theme.colors.primary) + private struct FloatingButtons: View { + let theme: AppTheme + let scrollView: EndlessScrollView + let chat: Chat + @Binding var loadingMoreItems: Bool + @Binding var loadingTopItems: Bool + @Binding var requestedTopScroll: Bool + @Binding var loadingBottomItems: Bool + @Binding var requestedBottomScroll: Bool + @Binding var animatedScrollingInProgress: Bool + let listState: EndlessScrollView.ListState + @ObservedObject var model: FloatingButtonModel + let reloadItems: () -> Void + + var body: some View { + ZStack(alignment: .top) { + if let date = model.date { + DateSeparator(date: date) + .padding(.vertical, 4).padding(.horizontal, 8) + .background(.thinMaterial) + .clipShape(Capsule()) + .opacity(model.isDateVisible ? 1 : 0) + .padding(.vertical, 4) } - .onTapGesture { - scrollModel.scrollToNextPage() - } - .contextMenu { - Button { - Task { - await markChatRead(chat) + VStack { + if model.unreadAbove > 0 && !animatedScrollingInProgress { + if loadingTopItems && requestedTopScroll { + circleButton { ProgressView() } + } else { + circleButton { + unreadCountText(model.unreadAbove) + .font(.callout) + .foregroundColor(theme.colors.primary) + } + .onTapGesture { + if loadingTopItems { + requestedTopScroll = true + requestedBottomScroll = false + } else { + scrollToTopUnread() + } + } + .contextMenu { + Button { + Task { + await markChatRead(chat) + } + } label: { + Label("Mark read", systemImage: "checkmark") + } + } + } + } + Spacer() + if listState.firstVisibleItemIndex != 0 && !animatedScrollingInProgress { + if loadingBottomItems && requestedBottomScroll { + circleButton { ProgressView() } + } else { + circleButton { + Group { + if model.unreadBelow > 0 { + unreadCountText(model.unreadBelow) + .font(.callout) + .foregroundColor(theme.colors.primary) + } else { + Image(systemName: "chevron.down").foregroundColor(theme.colors.primary) + } + } + } + .onTapGesture { + if loadingBottomItems || !ItemsModel.shared.lastItemsLoaded { + requestedTopScroll = false + requestedBottomScroll = true + } else { + scrollToBottom() + } + } } - } label: { - Label("Mark read", systemImage: "checkmark") } } + .padding() + .frame(maxWidth: .infinity, alignment: .trailing) } - Spacer() - if counts.unreadBelow > 0 { - circleButton { - unreadCountText(counts.unreadBelow) - .font(.callout) - .foregroundColor(theme.colors.primary) + .onChange(of: loadingTopItems) { loading in + if !loading && requestedTopScroll { + requestedTopScroll = false + scrollToTopUnread() } - .onTapGesture { - scrollModel.scrollToBottom() + } + .onChange(of: loadingBottomItems) { loading in + if !loading && requestedBottomScroll && ItemsModel.shared.lastItemsLoaded { + requestedBottomScroll = false + scrollToBottom() } - } else if !counts.isNearBottom { - circleButton { - Image(systemName: "chevron.down") - .foregroundColor(theme.colors.primary) + } + .onDisappear(perform: model.resetDate) + } + + private func scrollToTopUnread() { + Task { + if !ItemsModel.shared.chatState.splits.isEmpty { + await MainActor.run { loadingMoreItems = true } + await loadChat(chatId: chat.id, openAroundItemId: nil, clearItems: false) + await MainActor.run { reloadItems() } + if let index = listState.items.lastIndex(where: { $0.hasUnread() }) { + await MainActor.run { animatedScrollingInProgress = true } + await scrollView.scrollToItemAnimated(index) + await MainActor.run { animatedScrollingInProgress = false } + } + await MainActor.run { loadingMoreItems = false } + } else if let index = listState.items.lastIndex(where: { $0.hasUnread() }) { + await MainActor.run { animatedScrollingInProgress = true } + // scroll to the top unread item + await scrollView.scrollToItemAnimated(index) + await MainActor.run { animatedScrollingInProgress = false } + } else { + logger.debug("No more unread items, total: \(listState.items.count)") } - .onTapGesture { scrollModel.scrollToBottom() } } } - .padding() + + private func scrollToBottom() { + animatedScrollingInProgress = true + Task { + await scrollView.scrollToItemAnimated(0, top: false) + await MainActor.run { animatedScrollingInProgress = false } + } + } + + private func circleButton(_ content: @escaping () -> Content) -> some View { + ZStack { + Circle() + .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground)) + .frame(width: 44, height: 44) + content() + } + } } - private func circleButton(_ content: @escaping () -> Content) -> some View { - ZStack { - Circle() - .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground)) - .frame(width: 44, height: 44) - content() + private struct DateSeparator: View { + let date: Date + + var body: some View { + Text(String.localizedStringWithFormat( + NSLocalizedString("%@, %@", comment: "format for date separator in chat"), + date.formatted(.dateTime.weekday(.abbreviated)), + date.formatted( + Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) + ? .dateTime.day().month(.abbreviated) + : .dateTime.day().month(.abbreviated).year() + ) + )) + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) } } @@ -568,8 +903,8 @@ struct ChatView: View { private func endCallButton(_ call: Call) -> some View { Button { - if let uuid = call.callkitUUID { - CallController.shared.endCall(callUUID: uuid) + if CallController.useCallKit(), let callUUID = call.callUUID { + CallController.shared.endCall(callUUID: callUUID) } else { CallController.shared.endCall(call: call) {} } @@ -587,11 +922,29 @@ struct ChatView: View { } private func focusSearch() { - searchMode = true + showSearch = true searchFocussed = true searchText = "" } + private func closeSearch() { + showSearch = false + searchText = "" + searchFocussed = false + } + + private func closeKeyboardAndRun(_ action: @escaping () -> Void) { + var delay: TimeInterval = 0 + if keyboardVisible || keyboardHiddenDate.timeIntervalSinceNow >= -1 || showSearch { + delay = 0.5 + closeSearch() + hideKeyboard() + } + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + action() + } + } + private func addMembersButton() -> some View { Button { if case let .group(gInfo) = chat.chatInfo { @@ -648,43 +1001,148 @@ struct ChatView: View { } } - private func loadChatItems(_ cInfo: ChatInfo) { + private func forwardSelectedMessages() { Task { - if loadingItems || firstPage { return } - loadingItems = true do { - var reversedPage = Array() - var chatItemsAvailable = true - // Load additional items until the page is +50 large after merging - while chatItemsAvailable && filtered(reversedPage).count < loadItemsPerPage { - let pagination: ChatPagination = - if let lastItem = reversedPage.last ?? im.reversedChatItems.last { - .before(chatItemId: lastItem.id, count: loadItemsPerPage) - } else { - .last(count: loadItemsPerPage) - } - let chatItems = try await apiGetChatItems( - type: cInfo.chatType, - id: cInfo.apiId, - pagination: pagination, - search: searchText + if let selectedChatItems { + let (validItems, confirmation) = try await apiPlanForwardChatItems( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + itemIds: Array(selectedChatItems) ) - chatItemsAvailable = !chatItems.isEmpty - reversedPage.append(contentsOf: chatItems.reversed()) - } - await MainActor.run { - if reversedPage.count == 0 { - firstPage = true + if let confirmation { + if validItems.count > 0 { + showAlert( + String.localizedStringWithFormat( + NSLocalizedString("Forward %d message(s)?", comment: "alert title"), + validItems.count + ), + message: forwardConfirmationText(confirmation) + "\n" + + NSLocalizedString("Forward messages without files?", comment: "alert message") + ) { + switch confirmation { + case let .filesNotAccepted(fileIds): + [forwardAction(validItems), downloadAction(fileIds), cancelAlertAction] + default: + [forwardAction(validItems), cancelAlertAction] + } + } + } else { + showAlert( + NSLocalizedString("Nothing to forward!", comment: "alert title"), + message: forwardConfirmationText(confirmation) + ) { + switch confirmation { + case let .filesNotAccepted(fileIds): + [downloadAction(fileIds), cancelAlertAction] + default: + [okAlertAction] + } + } + } } else { - im.reversedChatItems.append(contentsOf: reversedPage) + await openForwardingSheet(validItems) } - loadingItems = false } - } catch let error { - logger.error("apiGetChat error: \(responseError(error))") - await MainActor.run { loadingItems = false } + } catch { + logger.error("Plan forward chat items failed: \(error.localizedDescription)") } } + + func forwardConfirmationText(_ fc: ForwardConfirmation) -> String { + switch fc { + case let .filesNotAccepted(fileIds): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) were not downloaded.", comment: "forward confirmation reason"), + fileIds.count + ) + case let .filesInProgress(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) are still being downloaded.", comment: "forward confirmation reason"), + filesCount + ) + case let .filesMissing(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) were deleted.", comment: "forward confirmation reason"), + filesCount + ) + case let .filesFailed(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) failed to download.", comment: "forward confirmation reason"), + filesCount + ) + } + } + + func forwardAction(_ items: [Int64]) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Forward messages", comment: "alert action"), + style: .default, + handler: { _ in Task { await openForwardingSheet(items) } } + ) + } + + func downloadAction(_ fileIds: [Int64]) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Download files", comment: "alert action"), + style: .default, + handler: { _ in + Task { + if let user = ChatModel.shared.currentUser { + await receiveFiles(user: user, fileIds: fileIds) + } + } + } + ) + } + + func openForwardingSheet(_ items: [Int64]) async { + let im = ItemsModel.shared + var items = Set(items) + var fci = [ChatItem]() + for reversedChatItem in im.reversedChatItems { + if items.contains(reversedChatItem.id) { + items.remove(reversedChatItem.id) + fci.insert(reversedChatItem, at: 0) + } + if items.isEmpty { break } + } + await MainActor.run { forwardedChatItems = fci } + } + } + + private func loadChatItems(_ chat: Chat, _ pagination: ChatPagination) async -> Bool { + if loadingMoreItems { return false } + await MainActor.run { + loadingMoreItems = true + if case .before = pagination { + loadingTopItems = true + } else if case .after = pagination { + loadingBottomItems = true + } + } + let triedToLoad = await loadChatItemsUnchecked(chat, pagination) + await MainActor.run { + loadingMoreItems = false + if case .before = pagination { + loadingTopItems = false + } else if case .after = pagination { + loadingBottomItems = false + } + } + return triedToLoad + } + + private func loadChatItemsUnchecked(_ chat: Chat, _ pagination: ChatPagination) async -> Bool { + await apiLoadMessages( + chat.chatInfo.id, + pagination, + im.chatState, + searchText, + nil, + { visibleItemIndexesNonReversed(scrollView.listState, mergedItems.boxedValue) } + ) + return true } func stopAudioPlayer() { @@ -692,152 +1150,350 @@ struct ChatView: View { VoiceItemState.chatView = [:] } + func onChatItemsUpdated() { + if !mergedItems.boxedValue.isActualState() { + //logger.debug("Items are not actual, waiting for the next update: \(String(describing: mergedItems.boxedValue.splits)) \(ItemsModel.shared.chatState.splits), \(mergedItems.boxedValue.indexInParentItems.count) vs \(ItemsModel.shared.reversedChatItems.count)") + return + } + floatingButtonModel.updateOnListChange(scrollView.listState) + preloadIfNeeded( + $allowLoadMoreItems, + $ignoreLoadingRequests, + scrollView.listState, + mergedItems, + loadItems: { unchecked, pagination in + if unchecked { + await loadChatItemsUnchecked(chat, pagination) + } else { + await loadChatItems(chat, pagination) + } + }, + loadLastItems: { + if !loadingMoreItems { + await loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) + } + } + ) + } + private struct ChatItemWithMenu: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileRadius = defaultProfileImageCorner @Binding @ObservedObject var chat: Chat + @ObservedObject var dummyModel: ChatItemDummyModel = .shared + let index: Int + let isLastItem: Bool let chatItem: ChatItem + let scrollToItemId: (ChatItem.ID) -> Void + let merged: MergedItem let maxWidth: CGFloat @Binding var composeState: ComposeState @Binding var selectedMember: GMember? - @Binding var revealedChatItem: ChatItem? + @Binding var showChatInfoSheet: Bool + @Binding var revealedItems: Set @State private var deletingItem: ChatItem? = nil @State private var showDeleteMessage = false @State private var deletingItems: [Int64] = [] @State private var showDeleteMessages = false + @State private var archivingReports: Set? = nil + @State private var showArchivingReports = false @State private var showChatItemInfoSheet: Bool = false @State private var chatItemInfo: ChatItemInfo? - @State private var showForwardingSheet: Bool = false + @State private var msgWidth: CGFloat = 0 + @State private var touchInProgress: Bool = false @Binding var selectedChatItems: Set? + @Binding var forwardedChatItems: [ChatItem] + + @Binding var searchText: String + var closeKeyboardAndRun: (@escaping () -> Void) -> Void @State private var allowMenu: Bool = true + @State private var markedRead = false + @State private var markReadTask: Task? = nil + @State private var actionSheet: SomeActionSheet? = nil - var revealed: Bool { chatItem == revealedChatItem } + var revealed: Bool { revealedItems.contains(chatItem.id) } + + typealias ItemSeparation = (timestamp: Bool, largeGap: Bool, date: Date?) + + private func reveal(_ yes: Bool) -> Void { + merged.revealItems(yes, $revealedItems) + } + + func getItemSeparation(_ chatItem: ChatItem, _ prevItem: ChatItem?) -> ItemSeparation { + guard let prevItem else { + return ItemSeparation(timestamp: true, largeGap: true, date: nil) + } + + let sameMemberAndDirection = if case .groupRcv(let prevGroupMember) = prevItem.chatDir, case .groupRcv(let groupMember) = chatItem.chatDir { + groupMember.groupMemberId == prevGroupMember.groupMemberId + } else { + chatItem.chatDir.sent == prevItem.chatDir.sent + } + let largeGap = !sameMemberAndDirection || prevItem.meta.itemTs.timeIntervalSince(chatItem.meta.itemTs) > 60 + + return ItemSeparation( + timestamp: largeGap || formatTimestampMeta(chatItem.meta.itemTs) != formatTimestampMeta(prevItem.meta.itemTs), + largeGap: largeGap, + date: Calendar.current.isDate(chatItem.meta.itemTs, inSameDayAs: prevItem.meta.itemTs) ? nil : prevItem.meta.itemTs + ) + } + + func shouldShowAvatar(_ current: ChatItem, _ older: ChatItem?) -> Bool { + let oldIsGroupRcv = switch older?.chatDir { + case .groupRcv: true + default: false + } + let sameMember = switch (older?.chatDir, current.chatDir) { + case (.groupRcv(let oldMember), .groupRcv(let member)): + oldMember.memberId == member.memberId + default: + false + } + if case .groupRcv = current.chatDir, (older == nil || (!oldIsGroupRcv || !sameMember)) { + return true + } else { + return false + } + } var body: some View { - let (currIndex, _) = m.getNextChatItem(chatItem) - let ciCategory = chatItem.mergeCategory - let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory) - let range = itemsRange(currIndex, prevHidden) let im = ItemsModel.shared - Group { - if revealed, let range = range { - let items = Array(zip(Array(range), im.reversedChatItems[range])) - ForEach(items.reversed(), id: \.1.viewId) { (i, ci) in - let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1] - chatItemView(ci, nil, prev) - .overlay { - if let selected = selectedChatItems, ci.canBeDeletedForSelf { - Color.clear - .contentShape(Rectangle()) - .onTapGesture { - let checked = selected.contains(ci.id) - selectUnselectChatItem(select: !checked, ci) - } - } - } - } - } else { - chatItemView(chatItem, range, prevItem) + + let last = isLastItem ? im.reversedChatItems.last : nil + let listItem = merged.newest() + let item = listItem.item + let range: ClosedRange? = if case let .grouped(_, _, _, rangeInReversed, _, _, _, _) = merged { + rangeInReversed.boxedValue + } else { + nil + } + let showAvatar = shouldShowAvatar(item, listItem.nextItem) + let single = switch merged { + case .single: true + default: false + } + let itemSeparation = getItemSeparation(item, single || revealed ? listItem.prevItem: nil) + return VStack(spacing: 0) { + if let last { + DateSeparator(date: last.meta.itemTs).padding(8) + } + chatItemListView(range, showAvatar, item, itemSeparation) .overlay { if let selected = selectedChatItems, chatItem.canBeDeletedForSelf { Color.clear .contentShape(Rectangle()) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { let checked = selected.contains(chatItem.id) selectUnselectChatItem(select: !checked, chatItem) - } + }) } } + if let date = itemSeparation.date { + DateSeparator(date: date).padding(8) } } .onAppear { + if markedRead { + return + } else { + markedRead = true + } if let range { - if let items = unreadItems(range) { + let (itemIds, unreadMentions) = unreadItemIds(range) + if !itemIds.isEmpty { waitToMarkRead { - for ci in items { - await apiMarkChatItemRead(chat.chatInfo, ci) - } + await apiMarkChatItemsRead(chat.chatInfo, itemIds, mentionsRead: unreadMentions) } } } else if chatItem.isRcvNew { waitToMarkRead { - await apiMarkChatItemRead(chat.chatInfo, chatItem) + await apiMarkChatItemsRead(chat.chatInfo, [chatItem.id], mentionsRead: chatItem.meta.userMention ? 1 : 0) } } } + .onDisappear { + markReadTask?.cancel() + markedRead = false + } + .actionSheet(item: $actionSheet) { $0.actionSheet } + // skip updating struct on touch if no need to show GoTo button + .if(touchInProgress || searchIsNotBlank || (chatItem.meta.itemForwarded != nil && chatItem.meta.itemForwarded != .unknown)) { + // long press listener steals taps from top-level listener, so repeating it's logic here as well + $0.onTapGesture { + hideKeyboard() + } + .onLongPressGesture(minimumDuration: .infinity, perform: {}, onPressingChanged: { pressing in + touchInProgress = pressing + }) + } } - - private func unreadItems(_ range: ClosedRange) -> [ChatItem]? { + + private func unreadItemIds(_ range: ClosedRange) -> ([ChatItem.ID], Int) { let im = ItemsModel.shared - let items = range.compactMap { i in - if i >= 0 && i < im.reversedChatItems.count { - let ci = im.reversedChatItems[i] - return if ci.isRcvNew { ci } else { nil } - } else { - return nil + var unreadItems: [ChatItem.ID] = [] + var unreadMentions: Int = 0 + + for i in range { + if i < 0 || i >= im.reversedChatItems.count { + break + } + let ci = im.reversedChatItems[i] + if ci.isRcvNew { + unreadItems.append(ci.id) + if ci.meta.userMention { + unreadMentions += 1 + } } } - return if items.isEmpty { nil } else { items } + + return (unreadItems, unreadMentions) } - + private func waitToMarkRead(_ op: @Sendable @escaping () async -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { - if m.chatId == chat.chatInfo.id { - Task(operation: op) + markReadTask = Task { + do { + _ = try await Task.sleep(nanoseconds: 600_000000) + if m.chatId == chat.chatInfo.id { + await op() + } + } catch { + // task was cancelled } } } - @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?) -> some View { + private var searchIsNotBlank: Bool { + get { + searchText.count > 0 && !searchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + } + + @available(iOS 16.0, *) + struct MemberLayout: Layout { + let spacing: Double + let msgWidth: Double + + private func sizes(subviews: Subviews, proposal: ProposedViewSize) -> (CGSize, CGSize) { + assert(subviews.count == 2, "member layout must contain exactly two subviews") + let roleSize = subviews[1].sizeThatFits(proposal) + let memberSize = subviews[0].sizeThatFits( + ProposedViewSize( + width: (proposal.width ?? msgWidth) - roleSize.width, + height: proposal.height + ) + ) + return (memberSize, roleSize) + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize { + let (memberSize, roleSize) = sizes(subviews: subviews, proposal: proposal) + return CGSize( + width: min( + proposal.width ?? msgWidth, + max(msgWidth, roleSize.width + spacing + memberSize.width) + ), + height: max(memberSize.height, roleSize.height) + ) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) { + let (memberSize, roleSize) = sizes(subviews: subviews, proposal: proposal) + subviews[0].place( + at: CGPoint(x: bounds.minX, y: bounds.midY - memberSize.height / 2), + proposal: ProposedViewSize(memberSize) + ) + subviews[1].place( + at: CGPoint( + x: bounds.minX + max(memberSize.width + spacing, msgWidth - roleSize.width), + y: bounds.midY - roleSize.height / 2 + ), + proposal: ProposedViewSize(roleSize) + ) + } + } + + @ViewBuilder func chatItemListView( + _ range: ClosedRange?, + _ showAvatar: Bool, + _ ci: ChatItem, + _ itemSeparation: ItemSeparation + ) -> some View { + let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2 if case let .groupRcv(member) = ci.chatDir, - case let .group(groupInfo) = chat.chatInfo { - let (prevMember, memCount): (GroupMember?, Int) = - if let range = range { - m.getPrevHiddenMember(member, range) - } else { - (nil, 1) - } - if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil { + case .group = chat.chatInfo { + if showAvatar { VStack(alignment: .leading, spacing: 4) { if ci.content.showMemberName { - let t = if memCount == 1 && member.memberRole > .member { - Text(member.memberRole.text + " ").fontWeight(.semibold) + Text(member.displayName) - } else { - Text(memberNames(member, prevMember, memCount)) + Group { + let (prevMember, memCount): (GroupMember?, Int) = + if let range = range { + m.getPrevHiddenMember(member, range) + } else { + (nil, 1) + } + if memCount == 1 && member.memberRole > .member { + Group { + if #available(iOS 16.0, *) { + MemberLayout(spacing: 16, msgWidth: msgWidth) { + Text(member.chatViewName) + .lineLimit(1) + Text(member.memberRole.text) + .fontWeight(.semibold) + .lineLimit(1) + .padding(.trailing, 8) + } + } else { + HStack(spacing: 16) { + Text(member.chatViewName) + .lineLimit(1) + Text(member.memberRole.text) + .fontWeight(.semibold) + .lineLimit(1) + .layoutPriority(1) + } + } + } + .frame( + maxWidth: maxWidth, + alignment: chatItem.chatDir.sent ? .trailing : .leading + ) + } else { + Text(memberNames(member, prevMember, memCount)) + .lineLimit(2) + } } - t .font(.caption) .foregroundStyle(.secondary) - .lineLimit(2) .padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0)) - .padding(.top, 7) + .padding(.top, 3) // this is in addition to message sequence gap } HStack(alignment: .center, spacing: 0) { if selectedChatItems != nil && ci.canBeDeletedForSelf { SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems) .padding(.trailing, 12) } - HStack(alignment: .top, spacing: 8) { + HStack(alignment: .top, spacing: 10) { MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) - .onTapGesture { - if let member = m.getGroupMember(member.groupMemberId) { - selectedMember = member + .simultaneousGesture(TapGesture().onEnded { + if let mem = m.getGroupMember(member.groupMemberId) { + selectedMember = mem } else { - Task { - await m.loadGroupMembers(groupInfo) { - selectedMember = m.getGroupMember(member.groupMemberId) - } - } + let mem = GMember.init(member) + m.groupMembers.append(mem) + m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 + selectedMember = mem } - } - chatItemWithMenu(ci, range, maxWidth) + }) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) + .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } } } } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) .padding(.trailing) .padding(.leading, 12) } else { @@ -846,11 +1502,11 @@ struct ChatView: View { SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems) .padding(.leading, 12) } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.trailing) - .padding(.leading, memberImageSize + 8 + 12) + .padding(.leading, 10 + memberImageSize + 12) } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) } } else { HStack(alignment: .center, spacing: 0) { @@ -863,10 +1519,10 @@ struct ChatView: View { .padding(.leading) } } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.horizontal) } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) } } @@ -881,19 +1537,29 @@ struct ChatView: View { } } - @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat) -> some View { + func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading - VStack(alignment: alignment.horizontal, spacing: 3) { - ChatItemView( - chat: chat, - chatItem: ci, - maxWidth: maxWidth, - revealed: .constant(revealed), - allowMenu: $allowMenu - ) - .modifier(ChatItemClipped(ci)) - .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } - .accessibilityLabel("") + return VStack(alignment: alignment.horizontal, spacing: 3) { + HStack { + if ci.chatDir.sent { + goToItemButton(true) + } + ChatItemView( + chat: chat, + chatItem: ci, + scrollToItemId: scrollToItemId, + maxWidth: maxWidth, + allowMenu: $allowMenu + ) + .environment(\.revealed, revealed) + .environment(\.showTimestamp, itemSeparation.timestamp) + .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap && (ci.meta.itemDeleted == nil || revealed))) + .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } + .accessibilityLabel("") + if !ci.chatDir.sent { + goToItemButton(false) + } + } if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 { chatItemReactions(ci) .padding(.bottom, 4) @@ -903,7 +1569,7 @@ struct ChatView: View { Button("Delete for me", role: .destructive) { deleteMessage(.cidmInternal, moderate: false) } - if let di = deletingItem, di.meta.deletable && !di.localNote { + if let di = deletingItem, di.meta.deletable && !di.localNote && !di.isReport { Button(broadcastDeleteButtonText(chat), role: .destructive) { deleteMessage(.cidmBroadcast, moderate: false) } @@ -914,20 +1580,28 @@ struct ChatView: View { deleteMessages(chat, deletingItems, moderate: false) } } + .confirmationDialog(archivingReports?.count == 1 ? "Archive report?" : "Archive \(archivingReports?.count ?? 0) reports?", isPresented: $showArchivingReports, titleVisibility: .visible) { + Button("For me", role: .destructive) { + if let reports = self.archivingReports { + archiveReports(chat.chatInfo, reports.sorted(), false) + self.archivingReports = [] + } + } + if case let ChatInfo.group(groupInfo) = chat.chatInfo, groupInfo.membership.memberActive { + Button("For all moderators", role: .destructive) { + if let reports = self.archivingReports { + archiveReports(chat.chatInfo, reports.sorted(), true) + self.archivingReports = [] + } + } + } + } .frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment) .frame(minWidth: 0, maxWidth: .infinity, alignment: alignment) .sheet(isPresented: $showChatItemInfoSheet, onDismiss: { chatItemInfo = nil }) { - ChatItemInfoView(ci: ci, chatItemInfo: $chatItemInfo) - } - .sheet(isPresented: $showForwardingSheet) { - if #available(iOS 16.0, *) { - ChatItemForwardingView(ci: ci, fromChatInfo: chat.chatInfo, composeState: $composeState) - .presentationDetents([.fraction(0.8)]) - } else { - ChatItemForwardingView(ci: ci, fromChatInfo: chat.chatInfo, composeState: $composeState) - } + ChatItemInfoView(ci: ci, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, chatItemInfo: $chatItemInfo) } } @@ -956,12 +1630,27 @@ struct ChatView: View { } .padding(.horizontal, 6) .padding(.vertical, 4) - - if chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted) { - v.onTapGesture { + .if(chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted)) { v in + v.simultaneousGesture(TapGesture().onEnded { setReaction(ci, add: !r.userReacted, reaction: r.reaction) + }) + } + switch chat.chatInfo { + case let .group(groupInfo): + v.contextMenu { + ReactionContextMenu( + groupInfo: groupInfo, + itemId: ci.id, + reactionCount: r, + selectedMember: $selectedMember, + profileRadius: profileRadius + ) } - } else { + case let .direct(contact): + v.contextMenu { + contactReactionMenu(contact, r) + } + default: v } } @@ -970,7 +1659,12 @@ struct ChatView: View { @ViewBuilder private func menu(_ ci: ChatItem, _ range: ClosedRange?, live: Bool) -> some View { - if let mc = ci.content.msgContent, ci.meta.itemDeleted == nil || revealed { + if case let .group(gInfo) = chat.chatInfo, ci.isReport, ci.meta.itemDeleted == nil { + if ci.chatDir != .groupSnd, gInfo.membership.memberRole >= .moderator { + archiveReportButton(ci) + } + deleteButton(ci, label: "Delete report") + } else if let mc = ci.content.msgContent, !ci.isReport, ci.meta.itemDeleted == nil || revealed { if chat.chatInfo.featureEnabled(.reactions) && ci.allowAddReaction, availableReactions.count > 0 { reactionsGroup @@ -1020,8 +1714,16 @@ struct ChatView: View { if !live || !ci.meta.isLive { deleteButton(ci) } - if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo), ci.chatDir != .groupSnd { - moderateButton(ci, groupInfo) + if ci.chatDir != .groupSnd { + if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) { + moderateButton(ci, groupInfo) + } else if ci.meta.itemDeleted == nil && chat.groupFeatureEnabled(.reports), + case let .group(gInfo) = chat.chatInfo, + gInfo.membership.memberRole == .member + && !live + && composeState.voiceMessageRecordingState == .noRecording { + reportButton(ci) + } } } else if ci.meta.itemDeleted != nil { if revealed { @@ -1069,7 +1771,7 @@ struct ChatView: View { var forwardButton: Button { Button { - showForwardingSheet = true + forwardedChatItems = [chatItem] } label: { Label( NSLocalizedString("Forward", comment: "chat item action"), @@ -1285,7 +1987,7 @@ struct ChatView: View { private func hideButton() -> Button { Button { withConditionalAnimation { - revealedChatItem = nil + reveal(false) } } label: { Label( @@ -1295,7 +1997,7 @@ struct ChatView: View { } } - private func deleteButton(_ ci: ChatItem) -> Button { + private func deleteButton(_ ci: ChatItem, label: LocalizedStringKey = "Delete") -> Button { Button(role: .destructive) { if !revealed, let currIndex = m.getChatItemIndex(ci), @@ -1317,10 +2019,7 @@ struct ChatView: View { deletingItem = ci } } label: { - Label( - NSLocalizedString("Delete", comment: "chat item action"), - systemImage: "trash" - ) + Label(label, systemImage: "trash") } } @@ -1339,10 +2038,10 @@ struct ChatView: View { AlertManager.shared.showAlert(Alert( title: Text("Delete member message?"), message: Text( - groupInfo.fullGroupPreferences.fullDelete.on - ? "The message will be deleted for all members." - : "The message will be marked as moderated for all members." - ), + groupInfo.fullGroupPreferences.fullDelete.on + ? "The message will be deleted for all members." + : "The message will be marked as moderated for all members." + ), primaryButton: .destructive(Text("Delete")) { deletingItem = ci deleteMessage(.cidmBroadcast, moderate: true) @@ -1357,10 +2056,19 @@ struct ChatView: View { } } + private func archiveReportButton(_ cItem: ChatItem) -> Button { + Button { + archivingReports = [cItem.id] + showArchivingReports = true + } label: { + Label("Archive report", systemImage: "archivebox") + } + } + private func revealButton(_ ci: ChatItem) -> Button { Button { withConditionalAnimation { - revealedChatItem = ci + reveal(true) } } label: { Label( @@ -1373,7 +2081,7 @@ struct ChatView: View { private func expandButton() -> Button { Button { withConditionalAnimation { - revealedChatItem = chatItem + reveal(true) } } label: { Label( @@ -1386,7 +2094,7 @@ struct ChatView: View { private func shrinkButton() -> Button { Button { withConditionalAnimation { - revealedChatItem = nil + reveal(false) } } label: { Label ( @@ -1396,6 +2104,37 @@ struct ChatView: View { } } + private func reportButton(_ ci: ChatItem) -> Button { + Button(role: .destructive) { + var buttons: [ActionSheet.Button] = ReportReason.supportedReasons.map { reason in + .default(Text(reason.text)) { + withAnimation { + if composeState.editing { + composeState = ComposeState(preview: .noPreview, contextItem: .reportedItem(chatItem: chatItem, reason: reason)) + } else { + composeState = composeState.copy(preview: .noPreview, contextItem: .reportedItem(chatItem: chatItem, reason: reason)) + } + } + } + } + + buttons.append(.cancel()) + + actionSheet = SomeActionSheet( + actionSheet: ActionSheet( + title: Text("Report reason?"), + buttons: buttons + ), + id: "reportChatMessage" + ) + } label: { + Label ( + NSLocalizedString("Report", comment: "chat item action"), + systemImage: "flag" + ) + } + } + var deleteMessagesTitle: LocalizedStringKey { let n = deletingItems.count return n == 1 ? "Delete message?" : "Delete \(n) messages?" @@ -1456,11 +2195,57 @@ struct ChatView: View { } else { m.removeChatItem(chat.chatInfo, itemDeletion.deletedChatItem.chatItem) } + let deletedItem = itemDeletion.deletedChatItem.chatItem + if deletedItem.isActiveReport { + m.decreaseGroupReportsCounter(chat.chatInfo.id) + } } } } } catch { - logger.error("ChatView.deleteMessage error: \(error.localizedDescription)") + logger.error("ChatView.deleteMessage error: \(error)") + } + } + } + + @ViewBuilder private func contactReactionMenu(_ contact: Contact, _ r: CIReactionCount) -> some View { + if !r.userReacted || r.totalReacted > 1 { + Button { showChatInfoSheet = true } label: { + profileMenuItem(Text(contact.displayName), contact.image, radius: profileRadius) + } + } + if r.userReacted { + Button {} label: { + profileMenuItem(Text("you"), m.currentUser?.profile.image, radius: profileRadius) + } + .disabled(true) + } + } + + func goToItemInnerButton(_ alignStart: Bool, _ image: String, touchInProgress: Bool, _ onClick: @escaping () -> Void) -> some View { + Image(systemName: image) + .resizable() + .frame(width: 13, height: 13) + .padding([alignStart ? .trailing : .leading], 10) + .tint(theme.colors.secondary.opacity(touchInProgress ? 1.0 : 0.4)) + .simultaneousGesture(TapGesture().onEnded(onClick)) + } + + @ViewBuilder + func goToItemButton(_ alignStart: Bool) -> some View { + let chatTypeApiIdMsgId = chatItem.meta.itemForwarded?.chatTypeApiIdMsgId + if searchIsNotBlank { + goToItemInnerButton(alignStart, "magnifyingglass", touchInProgress: touchInProgress) { + closeKeyboardAndRun { + ItemsModel.shared.loadOpenChatNoWait(chat.id, chatItem.id) + } + } + } else if let chatTypeApiIdMsgId { + goToItemInnerButton(alignStart, "arrow.right", touchInProgress: touchInProgress) { + closeKeyboardAndRun { + let (chatType, apiId, msgId) = chatTypeApiIdMsgId + ItemsModel.shared.loadOpenChatNoWait("\(chatType.rawValue)\(apiId)", msgId) + } } } } @@ -1519,6 +2304,10 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe } else { ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem) } + let deletedItem = di.deletedChatItem.chatItem + if deletedItem.isActiveReport { + ChatModel.shared.decreaseGroupReportsCounter(chat.chatInfo.id) + } } } await onSuccess() @@ -1529,6 +2318,37 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe } } +func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) { + if itemIds.count > 0 { + Task { + do { + let deleted = try await apiDeleteReceivedReports( + groupId: chatInfo.apiId, + itemIds: itemIds, + mode: forAll ? CIDeleteMode.cidmBroadcast : CIDeleteMode.cidmInternalMark + ) + + await MainActor.run { + for di in deleted { + if let toItem = di.toChatItem { + _ = ChatModel.shared.upsertChatItem(chatInfo, toItem.chatItem) + } else { + ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem) + } + let deletedItem = di.deletedChatItem.chatItem + if deletedItem.isActiveReport { + ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id) + } + } + } + await onSuccess() + } catch { + logger.error("ChatView.archiveReports error: \(error.localizedDescription)") + } + } + } +} + private func buildTheme() -> AppTheme { if let cId = ChatModel.shared.chatId, let chat = ChatModel.shared.getChat(cId) { let perChatTheme = if case let .direct(contact) = chat.chatInfo { @@ -1550,25 +2370,121 @@ private func buildTheme() -> AppTheme { } } +struct ReactionContextMenu: View { + @EnvironmentObject var m: ChatModel + let groupInfo: GroupInfo + var itemId: Int64 + var reactionCount: CIReactionCount + @Binding var selectedMember: GMember? + var profileRadius: CGFloat + @State private var memberReactions: [MemberReaction] = [] + + var body: some View { + groupMemberReactionList() + .task { + await loadChatItemReaction() + } + } + + @ViewBuilder private func groupMemberReactionList() -> some View { + if memberReactions.isEmpty { + ForEach(Array(repeating: 0, count: reactionCount.totalReacted), id: \.self) { _ in + textSpace + } + } else { + ForEach(memberReactions, id: \.groupMember.groupMemberId) { mr in + let mem = mr.groupMember + let userMember = mem.groupMemberId == groupInfo.membership.groupMemberId + Button { + if let member = m.getGroupMember(mem.groupMemberId) { + selectedMember = member + } else { + let member = GMember.init(mem) + m.groupMembers.append(member) + m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 + selectedMember = member + } + } label: { + profileMenuItem(Text(mem.displayName), mem.image, radius: profileRadius) + } + .disabled(userMember) + } + } + } + + private func loadChatItemReaction() async { + do { + let memberReactions = try await apiGetReactionMembers( + groupId: groupInfo.groupId, + itemId: itemId, + reaction: reactionCount.reaction + ) + await MainActor.run { + self.memberReactions = memberReactions + } + } catch let error { + logger.error("apiGetReactionMembers error: \(responseError(error))") + } + } +} + +func profileMenuItem(_ nameText: Text, _ image: String?, radius: CGFloat) -> some View { + HStack { + nameText + if let image, let img = imageFromBase64(image) { + Image(uiImage: maskToCustomShape(img, size: 30, radius: radius)) + } else { + Image(systemName: "person.crop.circle") + } + } +} + +func maskToCustomShape(_ image: UIImage, size: CGFloat, radius: CGFloat) -> UIImage { + let path = Path { path in + if radius >= 50 { + path.addEllipse(in: CGRect(x: 0, y: 0, width: size, height: size)) + } else if radius <= 0 { + path.addRect(CGRect(x: 0, y: 0, width: size, height: size)) + } else { + let cornerRadius = size * CGFloat(radius) / 100 + path.addRoundedRect( + in: CGRect(x: 0, y: 0, width: size, height: size), + cornerSize: CGSize(width: cornerRadius, height: cornerRadius), + style: .continuous + ) + } + } + + return UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { context in + context.cgContext.addPath(path.cgPath) + context.cgContext.clip() + let scale = size / max(image.size.width, image.size.height) + let imageSize = CGSize(width: image.size.width * scale, height: image.size.height * scale) + let imageOrigin = CGPoint( + x: (size - imageSize.width) / 2, + y: (size - imageSize.height) / 2 + ) + image.draw(in: CGRect(origin: imageOrigin, size: imageSize)) + } +} + struct ToggleNtfsButton: View { @ObservedObject var chat: Chat var body: some View { - Button { - toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) - } label: { - if chat.chatInfo.ntfsEnabled { - Label("Mute", systemImage: "speaker.slash") - } else { - Label("Unmute", systemImage: "speaker.wave.2") + if let nextMode = chat.chatInfo.nextNtfMode { + Button { + toggleNotifications(chat, enableNtfs: nextMode) + } label: { + Label(nextMode.text(mentions: chat.chatInfo.hasMentions), systemImage: nextMode.icon) } } } } -func toggleNotifications(_ chat: Chat, enableNtfs: Bool) { +func toggleNotifications(_ chat: Chat, enableNtfs: MsgFilter) { var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults - chatSettings.enableNtfs = enableNtfs ? .all : .none + chatSettings.enableNtfs = enableNtfs updateChatSettings(chat, chatSettings: chatSettings) } @@ -1583,6 +2499,9 @@ func updateChatSettings(_ chat: Chat, chatSettings: ChatSettings) { do { try await apiSetChatSettings(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, chatSettings: chatSettings) await MainActor.run { + let wasFavorite = chat.chatInfo.chatSettings?.favorite ?? false + ChatTagsModel.shared.updateChatFavorite(favorite: chatSettings.favorite, wasFavorite: wasFavorite) + let wasUnread = chat.unreadTag switch chat.chatInfo { case var .direct(contact): contact.chatSettings = chatSettings @@ -1592,6 +2511,7 @@ func updateChatSettings(_ chat: Chat, chatSettings: ChatSettings) { ChatModel.shared.updateGroup(groupInfo) default: () } + ChatTagsModel.shared.updateChatTagRead(chat, wasUnread: wasUnread) } } catch let error { logger.error("apiSetChatSettings error \(responseError(error))") diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift index df3a8caf55..14026d79d1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift @@ -18,7 +18,7 @@ struct ComposeImageView: View { var body: some View { HStack(alignment: .center, spacing: 8) { let imgs: [UIImage] = images.compactMap { image in - UIImage(base64Encoded: image) + imageFromBase64(image) } if imgs.count == 0 { ProgressView() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index f7f1a89299..e629a984df 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -18,7 +18,7 @@ struct ComposeLinkView: View { var body: some View { HStack(alignment: .center, spacing: 8) { - if let linkPreview = linkPreview { + if let linkPreview { linkPreviewView(linkPreview) } else { ProgressView() @@ -40,7 +40,7 @@ struct ComposeLinkView: View { private func linkPreviewView(_ linkPreview: LinkPreview) -> some View { HStack(alignment: .center, spacing: 8) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 78cae78cf5..8993de886f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -11,6 +11,8 @@ import SimpleXChat import SwiftyGif import PhotosUI +let MAX_NUMBER_OF_MENTIONS = 3 + enum ComposePreview { case noPreview case linkPreview(linkPreview: LinkPreview?) @@ -19,11 +21,12 @@ enum ComposePreview { case filePreview(fileName: String, file: URL) } -enum ComposeContextItem { +enum ComposeContextItem: Equatable { case noContextItem case quotedItem(chatItem: ChatItem) case editingItem(chatItem: ChatItem) - case forwardingItem(chatItem: ChatItem, fromChatInfo: ChatInfo) + case forwardingItems(chatItems: [ChatItem], fromChatInfo: ChatInfo) + case reportedItem(chatItem: ChatItem, reason: ReportReason) } enum VoiceMessageRecordingState { @@ -38,31 +41,41 @@ struct LiveMessage { var sentMsg: String? } +typealias MentionedMembers = [String: CIMention] + struct ComposeState { var message: String + var parsedMessage: [FormattedText] var liveMessage: LiveMessage? = nil var preview: ComposePreview var contextItem: ComposeContextItem var voiceMessageRecordingState: VoiceMessageRecordingState var inProgress = false var useLinkPreviews: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) + var mentions: MentionedMembers = [:] init( message: String = "", + parsedMessage: [FormattedText] = [], liveMessage: LiveMessage? = nil, preview: ComposePreview = .noPreview, contextItem: ComposeContextItem = .noContextItem, - voiceMessageRecordingState: VoiceMessageRecordingState = .noRecording + voiceMessageRecordingState: VoiceMessageRecordingState = .noRecording, + mentions: MentionedMembers = [:] ) { self.message = message + self.parsedMessage = parsedMessage self.liveMessage = liveMessage self.preview = preview self.contextItem = contextItem self.voiceMessageRecordingState = voiceMessageRecordingState + self.mentions = mentions } init(editingItem: ChatItem) { - self.message = editingItem.content.text + let text = editingItem.content.text + self.message = text + self.parsedMessage = editingItem.formattedText ?? FormattedText.plain(text) self.preview = chatItemPreview(chatItem: editingItem) self.contextItem = .editingItem(chatItem: editingItem) if let emc = editingItem.content.msgContent, @@ -71,31 +84,51 @@ struct ComposeState { } else { self.voiceMessageRecordingState = .noRecording } + self.mentions = editingItem.mentions ?? [:] } - init(forwardingItem: ChatItem, fromChatInfo: ChatInfo) { + init(forwardingItems: [ChatItem], fromChatInfo: ChatInfo) { self.message = "" + self.parsedMessage = [] self.preview = .noPreview - self.contextItem = .forwardingItem(chatItem: forwardingItem, fromChatInfo: fromChatInfo) + self.contextItem = .forwardingItems(chatItems: forwardingItems, fromChatInfo: fromChatInfo) self.voiceMessageRecordingState = .noRecording } func copy( message: String? = nil, + parsedMessage: [FormattedText]? = nil, liveMessage: LiveMessage? = nil, preview: ComposePreview? = nil, contextItem: ComposeContextItem? = nil, - voiceMessageRecordingState: VoiceMessageRecordingState? = nil + voiceMessageRecordingState: VoiceMessageRecordingState? = nil, + mentions: MentionedMembers? = nil ) -> ComposeState { ComposeState( message: message ?? self.message, + parsedMessage: parsedMessage ?? self.parsedMessage, liveMessage: liveMessage ?? self.liveMessage, preview: preview ?? self.preview, contextItem: contextItem ?? self.contextItem, - voiceMessageRecordingState: voiceMessageRecordingState ?? self.voiceMessageRecordingState + voiceMessageRecordingState: voiceMessageRecordingState ?? self.voiceMessageRecordingState, + mentions: mentions ?? self.mentions ) } - + + func mentionMemberName(_ name: String) -> String { + var n = 0 + var tryName = name + while mentions[tryName] != nil { + n += 1 + tryName = "\(name)_\(n)" + } + return tryName + } + + var memberMentions: [String: Int64] { + self.mentions.compactMapValues { $0.memberRef?.groupMemberId } + } + var editing: Bool { switch contextItem { case .editingItem: return true @@ -112,17 +145,35 @@ struct ComposeState { var forwarding: Bool { switch contextItem { - case .forwardingItem: return true + case .forwardingItems: return true default: return false } } - + + var reporting: Bool { + switch contextItem { + case .reportedItem: return true + default: return false + } + } + + var submittingValidReport: Bool { + switch contextItem { + case let .reportedItem(_, reason): + switch reason { + case .other: return !message.isEmpty + default: return true + } + default: return false + } + } + var sendEnabled: Bool { switch preview { case let .mediaPreviews(media): return !media.isEmpty case .voicePreview: return voiceMessageRecordingState == .finished case .filePreview: return true - default: return !message.isEmpty || forwarding || liveMessage != nil + default: return !message.isEmpty || forwarding || liveMessage != nil || submittingValidReport } } @@ -167,8 +218,15 @@ struct ComposeState { } } + var manyMediaPreviews: Bool { + switch preview { + case let .mediaPreviews(mediaPreviews): return mediaPreviews.count > 1 + default: return false + } + } + var attachmentDisabled: Bool { - if editing || forwarding || liveMessage != nil || inProgress { return true } + if editing || forwarding || liveMessage != nil || inProgress || reporting { return true } switch preview { case .noPreview: return false case .linkPreview: return false @@ -186,6 +244,15 @@ struct ComposeState { } } + var placeholder: String? { + switch contextItem { + case let .reportedItem(_, reason): + return reason.text + default: + return nil + } + } + var empty: Bool { message == "" && noPreview } @@ -258,6 +325,9 @@ struct ComposeView: View { @ObservedObject var chat: Chat @Binding var composeState: ComposeState @Binding var keyboardVisible: Bool + @Binding var keyboardHiddenDate: Date + @Binding var selectedRange: NSRange + var disabledText: LocalizedStringKey? = nil @State var linkUrl: URL? = nil @State var hasSimplexLink: Bool = false @@ -280,8 +350,8 @@ struct ComposeView: View { // this is a workaround to fire an explicit event in certain cases @State private var stopPlayback: Bool = false - @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true - @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true + @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial var body: some View { VStack(spacing: 0) { @@ -290,6 +360,11 @@ struct ComposeView: View { ContextInvitingContactMemberView() Divider() } + + if case let .reportedItem(_, reason) = composeState.contextItem { + reportReasonView(reason) + Divider() + } // preference checks should match checks in forwarding list let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files) @@ -317,9 +392,9 @@ struct ComposeView: View { Image(systemName: "paperclip") .resizable() } - .disabled(composeState.attachmentDisabled || !chat.userCanSend || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) + .disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) .frame(width: 25, height: 25) - .padding(.bottom, 12) + .padding(.bottom, 16) .padding(.leading, 12) .tint(theme.colors.primary) if case let .group(g) = chat.chatInfo, @@ -336,6 +411,7 @@ struct ComposeView: View { ZStack(alignment: .leading) { SendMessageView( composeState: $composeState, + selectedRange: $selectedRange, sendMessage: { ttl in sendMessage(ttl: ttl) resetLinkPreview() @@ -360,24 +436,19 @@ struct ComposeView: View { timedMessageAllowed: chat.chatInfo.featureEnabled(.timedMessages), onMediaAdded: { media in if !media.isEmpty { chosenMedia = media }}, keyboardVisible: $keyboardVisible, + keyboardHiddenDate: $keyboardHiddenDate, sendButtonColor: chat.chatInfo.incognito ? .indigo.opacity(colorScheme == .dark ? 1 : 0.7) : theme.colors.primary ) .padding(.trailing, 12) - .disabled(!chat.userCanSend) + .disabled(!chat.chatInfo.sendMsgEnabled) - if chat.userIsObserver { - Text("you are observer") + if let disabledText { + Text(disabledText) .italic() .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) - .onTapGesture { - AlertManager.shared.showAlertMsg( - title: "You can't send messages!", - message: "Please contact group admin." - ) - } } } } @@ -388,21 +459,23 @@ struct ComposeView: View { .ignoresSafeArea(.all, edges: .bottom) } .onChange(of: composeState.message) { msg in + let parsedMsg = parseSimpleXMarkdown(msg) + composeState = composeState.copy(parsedMessage: parsedMsg ?? FormattedText.plain(msg)) if composeState.linkPreviewAllowed { if msg.count > 0 { - showLinkPreview(msg) + showLinkPreview(parsedMsg) } else { resetLinkPreview() hasSimplexLink = false } } else if msg.count > 0 && !chat.groupFeatureEnabled(.simplexLinks) { - (_, hasSimplexLink) = parseMessage(msg) + (_, hasSimplexLink) = getSimplexLink(parsedMsg) } else { hasSimplexLink = false } } - .onChange(of: chat.userCanSend) { canSend in - if !canSend { + .onChange(of: chat.chatInfo.sendMsgEnabled) { sendEnabled in + if !sendEnabled { cancelCurrentVoiceRecording() clearCurrentDraft() clearState() @@ -452,7 +525,7 @@ struct ComposeView: View { Task { var media: [(String, UploadContent)] = [] for content in selected { - if let img = resizeImageToStrSize(content.uiImage, maxDataSize: 14000) { + if let img = await resizeImageToStrSize(content.uiImage, maxDataSize: 14000) { media.append((img, content)) await MainActor.run { composeState = composeState.copy(preview: .mediaPreviews(mediaPreviews: media)) @@ -544,7 +617,7 @@ struct ComposeView: View { } private func addMediaContent(_ content: UploadContent) async { - if let img = resizeImageToStrSize(content.uiImage, maxDataSize: 14000) { + if let img = await resizeImageToStrSize(content.uiImage, maxDataSize: 14000) { var newMedia: [(String, UploadContent?)] = [] if case var .mediaPreviews(media) = composeState.preview { media.append((img, content)) @@ -679,6 +752,27 @@ struct ComposeView: View { .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial) } + + + private func reportReasonView(_ reason: ReportReason) -> some View { + let reportText = switch reason { + case .spam: NSLocalizedString("Report spam: only group moderators will see it.", comment: "report reason") + case .profile: NSLocalizedString("Report member profile: only group moderators will see it.", comment: "report reason") + case .community: NSLocalizedString("Report violation: only group moderators will see it.", comment: "report reason") + case .illegal: NSLocalizedString("Report content: only group moderators will see it.", comment: "report reason") + case .other: NSLocalizedString("Report other: only group moderators will see it.", comment: "report reason") + case .unknown: "" // Should never happen + } + + return Text(reportText) + .italic() + .font(.caption) + .padding(12) + .frame(minHeight: 44) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.thinMaterial) + } + @ViewBuilder private func contextItemView() -> some View { switch composeState.contextItem { @@ -687,7 +781,7 @@ struct ComposeView: View { case let .quotedItem(chatItem: quotedItem): ContextItemView( chat: chat, - contextItem: quotedItem, + contextItems: [quotedItem], contextIcon: "arrowshape.turn.up.left", cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } ) @@ -695,18 +789,26 @@ struct ComposeView: View { case let .editingItem(chatItem: editingItem): ContextItemView( chat: chat, - contextItem: editingItem, + contextItems: [editingItem], contextIcon: "pencil", cancelContextItem: { clearState() } ) Divider() - case let .forwardingItem(chatItem: forwardedItem, _): + case let .forwardingItems(chatItems, _): ContextItemView( chat: chat, - contextItem: forwardedItem, + contextItems: chatItems, contextIcon: "arrowshape.turn.up.forward", + cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } + ) + Divider() + case let .reportedItem(chatItem: reportedItem, _): + ContextItemView( + chat: chat, + contextItems: [reportedItem], + contextIcon: "flag", cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }, - showSender: false + contextIconForeground: Color.red ) Divider() } @@ -724,21 +826,25 @@ struct ComposeView: View { var sent: ChatItem? let msgText = text ?? composeState.message let liveMessage = composeState.liveMessage + let mentions = composeState.memberMentions if !live { if liveMessage != nil { composeState = composeState.copy(liveMessage: nil) } await sending() } if chat.chatInfo.contact?.nextSendGrpInv ?? false { await sendMemberContactInvitation() - } else if case let .forwardingItem(ci, fromChatInfo) = composeState.contextItem { - sent = await forwardItem(ci, fromChatInfo, ttl) + } else if case let .forwardingItems(chatItems, fromChatInfo) = composeState.contextItem { + // Composed text is send as a reply to the last forwarded item + sent = await forwardItems(chatItems, fromChatInfo, ttl).last if !composeState.message.isEmpty { - sent = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl) + _ = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl, mentions: mentions) } } else if case let .editingItem(ci) = composeState.contextItem { sent = await updateMessage(ci, live: live) } else if let liveMessage = liveMessage, liveMessage.sentMsg != nil { sent = await updateMessage(liveMessage.chatItem, live: live) + } else if case let .reportedItem(chatItem, reason) = composeState.contextItem { + sent = await send(reason, chatItemId: chatItem.id) } else { var quoted: Int64? = nil if case let .quotedItem(chatItem: quotedItem) = composeState.contextItem { @@ -747,36 +853,39 @@ struct ComposeView: View { switch (composeState.preview) { case .noPreview: - sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl) + sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl, mentions: mentions) case .linkPreview: - sent = await send(checkLinkPreview(), quoted: quoted, live: live, ttl: ttl) - case let .mediaPreviews(mediaPreviews: media): + sent = await send(checkLinkPreview(), quoted: quoted, live: live, ttl: ttl, mentions: mentions) + case let .mediaPreviews(media): + // TODO: CHECK THIS let last = media.count - 1 + var msgs: [ComposedMessage] = [] if last >= 0 { for i in 0.. 0 { + // Sleep to allow `progressByTimeout` update be rendered + try? await Task.sleep(nanoseconds: 100_000000) + } + if let (fileSource, msgContent) = mediaContent(media[i], text: "") { + msgs.append(ComposedMessage(fileSource: fileSource, msgContent: msgContent)) } - _ = try? await Task.sleep(nanoseconds: 100_000000) } - if case (_, .video(_, _, _)) = media[last] { - sent = await sendVideo(media[last], text: msgText, quoted: quoted, live: live, ttl: ttl) - } else { - sent = await sendImage(media[last], text: msgText, quoted: quoted, live: live, ttl: ttl) + if let (fileSource, msgContent) = mediaContent(media[last], text: msgText) { + msgs.append(ComposedMessage(fileSource: fileSource, quotedItemId: quoted, msgContent: msgContent)) } } - if sent == nil { - sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl) + if msgs.isEmpty { + msgs = [ComposedMessage(quotedItemId: quoted, msgContent: .text(msgText))] } + sent = await send(msgs, live: live, ttl: ttl).last + case let .voicePreview(recordingFileName, duration): stopPlayback.toggle() let file = voiceCryptoFile(recordingFileName) - sent = await send(.voice(text: msgText, duration: duration), quoted: quoted, file: file, ttl: ttl) + sent = await send(.voice(text: msgText, duration: duration), quoted: quoted, file: file, ttl: ttl, mentions: mentions) case let .filePreview(_, file): if let savedFile = saveFileFromURL(file) { - sent = await send(.file(msgText), quoted: quoted, file: savedFile, live: live, ttl: ttl) + sent = await send(.file(msgText), quoted: quoted, file: savedFile, live: live, ttl: ttl, mentions: mentions) } } } @@ -791,6 +900,20 @@ struct ComposeView: View { } return sent + func mediaContent(_ media: (String, UploadContent?), text: String) -> (CryptoFile?, MsgContent)? { + let (previewImage, uploadContent) = media + return switch uploadContent { + case let .simpleImage(image): + (saveImage(image), .image(text: text, image: previewImage)) + case let .animatedImage(image): + (saveAnimImage(image), .image(text: text, image: previewImage)) + case let .video(_, url, duration): + (moveTempFileFromURL(url), .video(text: text, image: previewImage, duration: duration)) + case .none: + nil + } + } + func sending() async { await MainActor.run { composeState.inProgress = true } } @@ -817,7 +940,7 @@ struct ComposeView: View { type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, itemId: ei.id, - msg: mc, + updatedMessage: UpdatedMessage(msgContent: mc, mentions: composeState.memberMentions), live: live ) await MainActor.run { @@ -849,28 +972,13 @@ struct ComposeView: View { return .voice(text: msgText, duration: duration) case .file: return .file(msgText) + case .report(_, let reason): + return .report(text: msgText, reason: reason) case .unknown(let type, _): return .unknown(type: type, text: msgText) } } - func sendImage(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - let (image, data) = imageData - if let data = data, let savedFile = saveAnyImage(data) { - return await send(.image(text: text, image: image), quoted: quoted, file: savedFile, live: live, ttl: ttl) - } - return nil - } - - func sendVideo(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - let (image, data) = imageData - if case let .video(_, url, duration) = data, let savedFile = moveTempFileFromURL(url) { - ChatModel.shared.filesToDelete.remove(url) - return await send(.video(text: text, image: image, duration: duration), quoted: quoted, file: savedFile, live: live, ttl: ttl) - } - return nil - } - func voiceCryptoFile(_ fileName: String) -> CryptoFile? { if !privacyEncryptLocalFilesGroupDefault.get() { return CryptoFile.plain(fileName) @@ -885,52 +993,93 @@ struct ComposeView: View { return nil } } + + func send(_ reportReason: ReportReason, chatItemId: Int64) async -> ChatItem? { + if let chatItems = await apiReportMessage( + groupId: chat.chatInfo.apiId, + chatItemId: chatItemId, + reportReason: reportReason, + reportText: msgText + ) { + await MainActor.run { + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } + } + return chatItems.first + } + + return nil + } + + func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?, mentions: [String: Int64]) async -> ChatItem? { + await send( + [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc, mentions: mentions)], + live: live, + ttl: ttl + ).first + } - func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - if let chatItem = chat.chatInfo.chatType == .local - ? await apiCreateChatItem(noteFolderId: chat.chatInfo.apiId, file: file, msg: mc) - : await apiSendMessage( + func send(_ msgs: [ComposedMessage], live: Bool, ttl: Int?) async -> [ChatItem] { + if let chatItems = chat.chatInfo.chatType == .local + ? await apiCreateChatItems(noteFolderId: chat.chatInfo.apiId, composedMessages: msgs) + : await apiSendMessages( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, - file: file, - quotedItemId: quoted, - msg: mc, live: live, - ttl: ttl + ttl: ttl, + composedMessages: msgs ) { await MainActor.run { chatModel.removeLiveDummy(animated: false) - chatModel.addChatItem(chat.chatInfo, chatItem) + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } } - return chatItem + return chatItems } - if let file = file { - removeFile(file.filePath) + for msg in msgs { + if let file = msg.fileSource { + removeFile(file.filePath) + } } - return nil + return [] } - func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> ChatItem? { - if let chatItem = await apiForwardChatItem( + func forwardItems(_ forwardedItems: [ChatItem], _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> [ChatItem] { + if let chatItems = await apiForwardChatItems( toChatType: chat.chatInfo.chatType, toChatId: chat.chatInfo.apiId, fromChatType: fromChatInfo.chatType, fromChatId: fromChatInfo.apiId, - itemId: forwardedItem.id, + itemIds: forwardedItems.map { $0.id }, ttl: ttl ) { await MainActor.run { - chatModel.addChatItem(chat.chatInfo, chatItem) + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } + if forwardedItems.count != chatItems.count { + showAlert( + String.localizedStringWithFormat( + NSLocalizedString("%d messages not forwarded", comment: "alert title"), + forwardedItems.count - chatItems.count + ), + message: NSLocalizedString("Messages were deleted after you selected them.", comment: "alert message") + ) + } } - return chatItem + return chatItems + } else { + return [] } - return nil } func checkLinkPreview() -> MsgContent { switch (composeState.preview) { case let .linkPreview(linkPreview: linkPreview): - if let url = parseMessage(msgText).url, + if let parsedMsg = parseSimpleXMarkdown(msgText), + let url = getSimplexLink(parsedMsg).url, let linkPreview = linkPreview, url == linkPreview.uri { return .link(text: msgText, preview: linkPreview) @@ -941,14 +1090,6 @@ struct ComposeView: View { return .text(msgText) } } - - func saveAnyImage(_ img: UploadContent) -> CryptoFile? { - switch img { - case let .simpleImage(image): return saveImage(image) - case let .animatedImage(image): return saveAnimImage(image) - default: return nil - } - } } private func startVoiceMessageRecording() async { @@ -1057,9 +1198,9 @@ struct ComposeView: View { } } - private func showLinkPreview(_ s: String) { + private func showLinkPreview(_ parsedMsg: [FormattedText]?) { prevLinkUrl = linkUrl - (linkUrl, hasSimplexLink) = parseMessage(s) + (linkUrl, hasSimplexLink) = getSimplexLink(parsedMsg) if let url = linkUrl { if url != composeState.linkPreview?.uri && url != pendingLinkUrl { pendingLinkUrl = url @@ -1076,8 +1217,8 @@ struct ComposeView: View { } } - private func parseMessage(_ msg: String) -> (url: URL?, hasSimplexLink: Bool) { - guard let parsedMsg = parseSimpleXMarkdown(msg) else { return (nil, false) } + private func getSimplexLink(_ parsedMsg: [FormattedText]?) -> (url: URL?, hasSimplexLink: Bool) { + guard let parsedMsg else { return (nil, false) } let url: URL? = if let uri = parsedMsg.first(where: { ft in ft.format == .uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) }) { @@ -1108,11 +1249,14 @@ struct ComposeView: View { if pendingLinkUrl == url { composeState = composeState.copy(preview: .linkPreview(linkPreview: nil)) getLinkPreview(url: url) { linkPreview in - if let linkPreview = linkPreview, - pendingLinkUrl == url { + if let linkPreview, pendingLinkUrl == url { composeState = composeState.copy(preview: .linkPreview(linkPreview: linkPreview)) - pendingLinkUrl = nil + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + composeState = composeState.copy(preview: .noPreview) + } } + pendingLinkUrl = nil } } } @@ -1129,18 +1273,23 @@ struct ComposeView_Previews: PreviewProvider { static var previews: some View { let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) @State var composeState = ComposeState(message: "hello") + @State var selectedRange = NSRange() return Group { ComposeView( chat: chat, composeState: $composeState, - keyboardVisible: Binding.constant(true) + keyboardVisible: Binding.constant(true), + keyboardHiddenDate: Binding.constant(Date.now), + selectedRange: $selectedRange ) .environmentObject(ChatModel()) ComposeView( chat: chat, composeState: $composeState, - keyboardVisible: Binding.constant(true) + keyboardVisible: Binding.constant(true), + keyboardHiddenDate: Binding.constant(Date.now), + selectedRange: $selectedRange ) .environmentObject(ChatModel()) } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index 6245bbe21f..845442c75f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -12,9 +12,10 @@ import SimpleXChat struct ContextItemView: View { @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat - let contextItem: ChatItem + let contextItems: [ChatItem] let contextIcon: String let cancelContextItem: () -> Void + var contextIconForeground: Color? = nil var showSender: Bool = true var body: some View { @@ -23,14 +24,23 @@ struct ContextItemView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) - .foregroundColor(theme.colors.secondary) - if showSender, let sender = contextItem.memberDisplayName { - VStack(alignment: .leading, spacing: 4) { - Text(sender).font(.caption).foregroundColor(theme.colors.secondary) - msgContentView(lines: 2) - } + .foregroundColor(contextIconForeground ?? theme.colors.secondary) + if let singleItem = contextItems.first, contextItems.count == 1 { + if showSender, let sender = singleItem.memberDisplayName { + VStack(alignment: .leading, spacing: 4) { + Text(sender).font(.caption).foregroundColor(theme.colors.secondary) + msgContentView(lines: 2, contextItem: singleItem) + } + } else { + msgContentView(lines: 3, contextItem: singleItem) + } } else { - msgContentView(lines: 3) + Text( + chat.chatInfo.chatType == .local + ? "Saving \(contextItems.count) messages" + : "Forwarding \(contextItems.count) messages" + ) + .italic() } Spacer() Button { @@ -45,29 +55,40 @@ struct ContextItemView: View { .padding(12) .frame(minHeight: 54) .frame(maxWidth: .infinity) - .background(chatItemFrameColor(contextItem, theme)) + .background(background) } - private func msgContentView(lines: Int) -> some View { - contextMsgPreview() + private var background: Color { + contextItems.first + .map { chatItemFrameColor($0, theme) } + ?? Color(uiColor: .tertiarySystemBackground) + } + + private func msgContentView(lines: Int, contextItem: ChatItem) -> some View { + contextMsgPreview(contextItem) .multilineTextAlignment(isRightToLeft(contextItem.text) ? .trailing : .leading) .lineLimit(lines) } - private func contextMsgPreview() -> Text { - return attachment() + messageText(contextItem.text, contextItem.formattedText, nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) + private func contextMsgPreview(_ contextItem: ChatItem) -> some View { + let r = messageText(contextItem.text, contextItem.formattedText, sender: nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(background)) + let t = attachment() + Text(AttributedString(r.string)) + return t.if(r.hasSecrets, transform: hiddenSecretsView) func attachment() -> Text { + let isFileLoaded = if let fileSource = getLoadedFileSource(contextItem.file) { + FileManager.default.fileExists(atPath: getAppFilePath(fileSource.filePath).path) + } else { false } switch contextItem.content.msgContent { - case .file: return image("doc.fill") + case .file: return isFileLoaded ? image("doc.fill") : Text("") case .image: return image("photo") - case .voice: return image("play.fill") + case .voice: return isFileLoaded ? image("play.fill") : Text("") default: return Text("") } } func image(_ s: String) -> Text { - Text(Image(systemName: s)).foregroundColor(Color(uiColor: .tertiaryLabel)) + Text(" ") + Text(Image(systemName: s)).foregroundColor(Color(uiColor: .tertiaryLabel)) + textSpace } } } @@ -75,6 +96,6 @@ struct ContextItemView: View { struct ContextItemView_Previews: PreviewProvider { static var previews: some View { let contextItem: ChatItem = ChatItem.getSample(1, .directSnd, .now, "hello") - return ContextItemView(chat: Chat.sampleData, contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {}) + return ContextItemView(chat: Chat.sampleData, contextItems: [contextItem], contextIcon: "pencil.circle", cancelContextItem: {}, contextIconForeground: Color.red) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift index ad47b7351a..d809fd7b76 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift @@ -16,18 +16,15 @@ struct NativeTextEditor: UIViewRepresentable { @Binding var disableEditing: Bool @Binding var height: CGFloat @Binding var focused: Bool + @Binding var lastUnfocusedDate: Date + @Binding var placeholder: String? + @Binding var selectedRange: NSRange let onImagesAdded: ([UploadContent]) -> Void - private let minHeight: CGFloat = 37 + static let minHeight: CGFloat = 39 - private let defaultHeight: CGFloat = { - let field = CustomUITextField(height: Binding.constant(0)) - field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4) - return min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, 37), 360).rounded(.down) - }() - - func makeUIView(context: Context) -> UITextView { - let field = CustomUITextField(height: _height) + func makeUIView(context: Context) -> CustomUITextField { + let field = CustomUITextField(parent: self, height: _height) field.backgroundColor = .clear field.text = text field.textAlignment = alignment(text) @@ -36,10 +33,9 @@ struct NativeTextEditor: UIViewRepresentable { if !disableEditing { text = newText field.textAlignment = alignment(text) - updateFont(field) + field.updateFont() // Speed up the process of updating layout, reduce jumping content on screen - updateHeight(field) - self.height = field.frame.size.height + field.updateHeight() } else { field.text = text } @@ -47,42 +43,32 @@ struct NativeTextEditor: UIViewRepresentable { onImagesAdded(images) } } - field.setOnFocusChangedListener { focused = $0 } + field.setOnFocusChangedListener { + focused = $0 + if !focused { + lastUnfocusedDate = .now + } + } field.delegate = field field.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 6, right: 4) - updateFont(field) - updateHeight(field) + field.setPlaceholderView() + field.updateFont() + field.updateHeight(updateBindingNow: false) return field } - func updateUIView(_ field: UITextView, context: Context) { + func updateUIView(_ field: CustomUITextField, context: Context) { if field.markedTextRange == nil && field.text != text { field.text = text field.textAlignment = alignment(text) - updateFont(field) - updateHeight(field) + field.updateFont() + field.updateHeight(updateBindingNow: false) } - } - - private func updateHeight(_ field: UITextView) { - let maxHeight = min(360, field.font!.lineHeight * 12) - // When having emoji in text view and then removing it, sizeThatFits shows previous size (too big for empty text view), so using work around with default size - let newHeight = field.text == "" - ? defaultHeight - : min(max(field.sizeThatFits(CGSizeMake(field.frame.size.width, CGFloat.greatestFiniteMagnitude)).height, minHeight), maxHeight).rounded(.down) - - if field.frame.size.height != newHeight { - field.frame.size = CGSizeMake(field.frame.size.width, newHeight) - (field as! CustomUITextField).invalidateIntrinsicContentHeight(newHeight) + if field.placeholder != placeholder { + field.placeholder = placeholder } - } - - private func updateFont(_ field: UITextView) { - let newFont = isShortEmoji(field.text) - ? (field.text.count < 4 ? largeEmojiUIFont : mediumEmojiUIFont) - : UIFont.preferredFont(forTextStyle: .body) - if field.font != newFont { - field.font = newFont + if field.selectedRange != selectedRange { + field.selectedRange = selectedRange } } } @@ -91,17 +77,26 @@ private func alignment(_ text: String) -> NSTextAlignment { isRightToLeft(text) ? .right : .left } -private class CustomUITextField: UITextView, UITextViewDelegate { +class CustomUITextField: UITextView, UITextViewDelegate { + var parent: NativeTextEditor? var height: Binding var newHeight: CGFloat = 0 var onTextChanged: (String, [UploadContent]) -> Void = { newText, image in } var onFocusChanged: (Bool) -> Void = { focused in } - - init(height: Binding) { + + private let placeholderLabel: UILabel = UILabel() + + init(parent: NativeTextEditor?, height: Binding) { + self.parent = parent self.height = height super.init(frame: .zero, textContainer: nil) } + var placeholder: String? { + get { placeholderLabel.text } + set { placeholderLabel.text = newValue } + } + required init?(coder: NSCoder) { fatalError("Not implemented") } @@ -114,16 +109,63 @@ private class CustomUITextField: UITextView, UITextViewDelegate { invalidateIntrinsicContentSize() } - override var intrinsicContentSize: CGSize { - if height.wrappedValue != newHeight { - DispatchQueue.main.asyncAfter(deadline: .now(), execute: { self.height.wrappedValue = self.newHeight }) + func updateHeight(updateBindingNow: Bool = true) { + let maxHeight = min(360, font!.lineHeight * 12) + let newHeight = min(max(sizeThatFits(CGSizeMake(frame.size.width, CGFloat.greatestFiniteMagnitude)).height, NativeTextEditor.minHeight), maxHeight).rounded(.down) + + if self.newHeight != newHeight { + frame.size = CGSizeMake(frame.size.width, newHeight) + invalidateIntrinsicContentHeight(newHeight) + if updateBindingNow { + self.height.wrappedValue = newHeight + } else { + DispatchQueue.main.async { + self.height.wrappedValue = newHeight + } + } } - return CGSizeMake(0, newHeight) + } + + func updateFont() { + let newFont = isShortEmoji(text) + ? (text.count < 4 ? largeEmojiUIFont : mediumEmojiUIFont) + : UIFont.preferredFont(forTextStyle: .body) + if font != newFont { + font = newFont + // force apply new font because it has problem with doing it when the field had two emojis + if text.count == 0 { + text = " " + text = "" + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + updateHeight() + } + + override var intrinsicContentSize: CGSize { + CGSizeMake(0, newHeight) } func setOnTextChangedListener(onTextChanged: @escaping (String, [UploadContent]) -> Void) { self.onTextChanged = onTextChanged } + + func setPlaceholderView() { + placeholderLabel.textColor = .lightGray + placeholderLabel.font = UIFont.preferredFont(forTextStyle: .body) + placeholderLabel.isHidden = !text.isEmpty + placeholderLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(placeholderLabel) + + NSLayoutConstraint.activate([ + placeholderLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 7), + placeholderLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -7), + placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8) + ]) + } func setOnFocusChangedListener(onFocusChanged: @escaping (Bool) -> Void) { self.onFocusChanged = onFocusChanged @@ -172,6 +214,7 @@ private class CustomUITextField: UITextView, UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { + placeholderLabel.isHidden = !text.isEmpty if textView.markedTextRange == nil { var images: [UploadContent] = [] var rangeDiff = 0 @@ -203,10 +246,22 @@ private class CustomUITextField: UITextView, UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { onFocusChanged(true) + updateSelectedRange(textView) } func textViewDidEndEditing(_ textView: UITextView) { onFocusChanged(false) + updateSelectedRange(textView) + } + + func textViewDidChangeSelection(_ textView: UITextView) { + updateSelectedRange(textView) + } + + private func updateSelectedRange(_ textView: UITextView) { + if parent?.selectedRange != textView.selectedRange { + parent?.selectedRange = textView.selectedRange + } } } @@ -217,6 +272,9 @@ struct NativeTextEditor_Previews: PreviewProvider{ disableEditing: Binding.constant(false), height: Binding.constant(100), focused: Binding.constant(false), + lastUnfocusedDate: Binding.constant(.now), + placeholder: Binding.constant("Placeholder"), + selectedRange: Binding.constant(NSRange(location: 0, length: 0)), onImagesAdded: { _ in } ) .fixedSize(horizontal: false, vertical: true) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 9ad6e986bd..e7b02c9aea 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -13,7 +13,9 @@ private let liveMsgInterval: UInt64 = 3000_000000 struct SendMessageView: View { @Binding var composeState: ComposeState + @Binding var selectedRange: NSRange @EnvironmentObject var theme: AppTheme + @Environment(\.isEnabled) var isEnabled var sendMessage: (Int?) -> Void var sendLiveMessage: (() async -> Void)? = nil var updateLiveMessage: (() async -> Void)? = nil @@ -31,8 +33,9 @@ struct SendMessageView: View { @State private var holdingVMR = false @Namespace var namespace @Binding var keyboardVisible: Bool + @Binding var keyboardHiddenDate: Date var sendButtonColor = Color.accentColor - @State private var teHeight: CGFloat = 42 + @State private var teHeight: CGFloat = NativeTextEditor.minHeight @State private var teFont: Font = .body @State private var sendButtonSize: CGFloat = 29 @State private var sendButtonOpacity: CGFloat = 1 @@ -40,54 +43,57 @@ struct SendMessageView: View { @State private var showCustomTimePicker = false @State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get() @State private var progressByTimeout = false - @AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false + @UserDefault(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false var body: some View { - ZStack { - let composeShape = RoundedRectangle(cornerSize: CGSize(width: 20, height: 20)) - HStack(alignment: .bottom) { - ZStack(alignment: .leading) { - if case .voicePreview = composeState.preview { - Text("Voice message…") - .font(teFont.italic()) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.secondary) - .padding(.horizontal, 10) - .padding(.vertical, 8) - .frame(maxWidth: .infinity) - } else { - NativeTextEditor( - text: $composeState.message, - disableEditing: $composeState.inProgress, - height: $teHeight, - focused: $keyboardVisible, - onImagesAdded: onMediaAdded - ) - .allowsTightening(false) - .fixedSize(horizontal: false, vertical: true) - } - } - if progressByTimeout { - ProgressView() - .scaleEffect(1.4) - .frame(width: 31, height: 31, alignment: .center) - .padding([.bottom, .trailing], 3) - } else { - VStack(alignment: .trailing) { - if teHeight > 100 && !composeState.inProgress { - deleteTextButton() - Spacer() - } - composeActionButtons() - } - .frame(height: teHeight, alignment: .bottom) - } + let composeShape = RoundedRectangle(cornerSize: CGSize(width: 20, height: 20)) + ZStack(alignment: .leading) { + if case .voicePreview = composeState.preview { + Text("Voice message…") + .font(teFont.italic()) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.secondary) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .padding(.trailing, 32) + .frame(maxWidth: .infinity) + } else { + NativeTextEditor( + text: $composeState.message, + disableEditing: $composeState.inProgress, + height: $teHeight, + focused: $keyboardVisible, + lastUnfocusedDate: $keyboardHiddenDate, + placeholder: Binding(get: { composeState.placeholder }, set: { _ in }), + selectedRange: $selectedRange, + onImagesAdded: onMediaAdded + ) + .padding(.trailing, 32) + .allowsTightening(false) + .fixedSize(horizontal: false, vertical: true) } - .padding(.vertical, 1) - .background(theme.colors.background) - .clipShape(composeShape) - .overlay(composeShape.strokeBorder(.secondary, lineWidth: 0.5).opacity(0.7)) } + .overlay(alignment: .topTrailing, content: { + if !progressByTimeout && teHeight > 100 && !composeState.inProgress { + deleteTextButton() + } + }) + .overlay(alignment: .bottomTrailing, content: { + if progressByTimeout { + ProgressView() + .scaleEffect(1.4) + .frame(width: 31, height: 31, alignment: .center) + .padding([.bottom, .trailing], 4) + } else { + composeActionButtons() + // required for intercepting clicks + .background(.white.opacity(0.000001)) + } + }) + .padding(.vertical, 1) + .background(theme.colors.background) + .clipShape(composeShape) + .overlay(composeShape.strokeBorder(.secondary, lineWidth: 0.5).opacity(0.7)) .onChange(of: composeState.message, perform: { text in updateFont(text) }) .onChange(of: composeState.inProgress) { inProgress in if inProgress { @@ -105,6 +111,8 @@ struct SendMessageView: View { let vmrs = composeState.voiceMessageRecordingState if nextSendGrpInv { inviteMemberContactButton() + } else if case .reportedItem = composeState.contextItem { + sendMessageButton() } else if showVoiceMessageButton && composeState.message.isEmpty && !composeState.editing @@ -164,7 +172,7 @@ struct SendMessageView: View { !composeState.sendEnabled || composeState.inProgress ) - .frame(width: 29, height: 29) + .frame(width: 31, height: 31) .padding([.bottom, .trailing], 4) } @@ -187,7 +195,7 @@ struct SendMessageView: View { composeState.endLiveDisabled || disableSendButton ) - .frame(width: 29, height: 29) + .frame(width: 31, height: 31) .contextMenu{ sendButtonContextMenuItems() } @@ -227,6 +235,7 @@ struct SendMessageView: View { !composeState.editing { if case .noContextItem = composeState.contextItem, !composeState.voicePreview, + !composeState.manyMediaPreviews, let send = sendLiveMessage, let update = updateLiveMessage { Button { @@ -247,6 +256,7 @@ struct SendMessageView: View { } private struct RecordVoiceMessageButton: View { + @Environment(\.isEnabled) var isEnabled @EnvironmentObject var theme: AppTheme var startVoiceMessageRecording: (() -> Void)? var finishVoiceMessageRecording: (() -> Void)? @@ -255,15 +265,14 @@ struct SendMessageView: View { @State private var pressed: TimeInterval? = nil var body: some View { - Button(action: {}) { - Image(systemName: "mic.fill") - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .foregroundColor(theme.colors.primary) - } + Image(systemName: isEnabled ? "mic.fill" : "mic") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary) + .opacity(holdingVMR ? 0.7 : 1) .disabled(disabled) - .frame(width: 29, height: 29) + .frame(width: 31, height: 31) .padding([.bottom, .trailing], 4) ._onButtonGesture { down in if down { @@ -271,9 +280,7 @@ struct SendMessageView: View { pressed = ProcessInfo.processInfo.systemUptime startVoiceMessageRecording?() } else { - let now = ProcessInfo.processInfo.systemUptime - if let pressed = pressed, - now - pressed >= 1 { + if let pressed, ProcessInfo.processInfo.systemUptime - pressed >= 1 { finishVoiceMessageRecording?() } holdingVMR = false @@ -319,7 +326,7 @@ struct SendMessageView: View { .foregroundColor(theme.colors.secondary) } .disabled(composeState.inProgress) - .frame(width: 29, height: 29) + .frame(width: 31, height: 31) .padding([.bottom, .trailing], 4) } @@ -347,7 +354,7 @@ struct SendMessageView: View { Image(systemName: "bolt.fill") .resizable() .scaledToFit() - .foregroundColor(theme.colors.primary) + .foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary) .frame(width: 20, height: 20) } .frame(width: 29, height: 29) @@ -404,7 +411,7 @@ struct SendMessageView: View { .foregroundColor(theme.colors.primary) } .disabled(composeState.inProgress) - .frame(width: 29, height: 29) + .frame(width: 31, height: 31) .padding([.bottom, .trailing], 4) } @@ -420,8 +427,10 @@ struct SendMessageView: View { struct SendMessageView_Previews: PreviewProvider { static var previews: some View { @State var composeStateNew = ComposeState() + @State var selectedRange = NSRange() let ci = ChatItem.getSample(1, .directSnd, .now, "hello") @State var composeStateEditing = ComposeState(editingItem: ci) + @State var selectedRangeEditing = NSRange() @State var sendEnabled: Bool = true return Group { @@ -430,9 +439,11 @@ struct SendMessageView_Previews: PreviewProvider { Spacer(minLength: 0) SendMessageView( composeState: $composeStateNew, + selectedRange: $selectedRange, sendMessage: { _ in }, onMediaAdded: { _ in }, - keyboardVisible: Binding.constant(true) + keyboardVisible: Binding.constant(true), + keyboardHiddenDate: Binding.constant(Date.now) ) } VStack { @@ -440,9 +451,11 @@ struct SendMessageView_Previews: PreviewProvider { Spacer(minLength: 0) SendMessageView( composeState: $composeStateEditing, + selectedRange: $selectedRangeEditing, sendMessage: { _ in }, onMediaAdded: { _ in }, - keyboardVisible: Binding.constant(true) + keyboardVisible: Binding.constant(true), + keyboardHiddenDate: Binding.constant(Date.now) ) } } diff --git a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift index b3fab958bc..e4489e46ee 100644 --- a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift @@ -14,9 +14,10 @@ struct ContactPreferencesView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var contact: Contact - @State var featuresAllowed: ContactFeaturesAllowed - @State var currentFeaturesAllowed: ContactFeaturesAllowed + @Binding var featuresAllowed: ContactFeaturesAllowed + @Binding var currentFeaturesAllowed: ContactFeaturesAllowed @State private var showSaveDialogue = false + let savePreferences: () -> Void var body: some View { let user: User = chatModel.currentUser! @@ -48,7 +49,10 @@ struct ContactPreferencesView: View { savePreferences() dismiss() } - Button("Exit without saving") { dismiss() } + Button("Exit without saving") { + featuresAllowed = currentFeaturesAllowed + dismiss() + } } } @@ -118,31 +122,15 @@ struct ContactPreferencesView: View { private func featureFooter(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View { Text(feature.enabledDescription(enabled)) } - - private func savePreferences() { - Task { - do { - let prefs = contactFeaturesAllowedToPrefs(featuresAllowed) - if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) { - await MainActor.run { - contact = toContact - chatModel.updateContact(toContact) - currentFeaturesAllowed = featuresAllowed - } - } - } catch { - logger.error("ContactPreferencesView apiSetContactPrefs error: \(responseError(error))") - } - } - } } struct ContactPreferencesView_Previews: PreviewProvider { static var previews: some View { ContactPreferencesView( contact: Binding.constant(Contact.sampleData), - featuresAllowed: ContactFeaturesAllowed.sampleData, - currentFeaturesAllowed: ContactFeaturesAllowed.sampleData + featuresAllowed: Binding.constant(ContactFeaturesAllowed.sampleData), + currentFeaturesAllowed: Binding.constant(ContactFeaturesAllowed.sampleData), + savePreferences: {} ) } } diff --git a/apps/ios/Shared/Views/Chat/EndlessScrollView.swift b/apps/ios/Shared/Views/Chat/EndlessScrollView.swift new file mode 100644 index 0000000000..cc61754b26 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/EndlessScrollView.swift @@ -0,0 +1,715 @@ +// +// EndlessScrollView.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 25.01.2025. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct ScrollRepresentable: UIViewControllerRepresentable where ScrollItem : Identifiable, ScrollItem: Hashable { + + let scrollView: EndlessScrollView + let content: (Int, ScrollItem) -> Content + + func makeUIViewController(context: Context) -> ScrollController { + ScrollController.init(scrollView: scrollView, content: content) + } + + func updateUIViewController(_ controller: ScrollController, context: Context) {} + + class ScrollController: UIViewController { + let scrollView: EndlessScrollView + fileprivate var items: [ScrollItem] = [] + fileprivate var content: ((Int, ScrollItem) -> Content)! + + fileprivate init(scrollView: EndlessScrollView, content: @escaping (Int, ScrollItem) -> Content) { + self.scrollView = scrollView + self.content = content + super.init(nibName: nil, bundle: nil) + self.view = scrollView + scrollView.createCell = createCell + scrollView.updateCell = updateCell + } + + required init?(coder: NSCoder) { fatalError() } + + private func createCell(_ index: Int, _ items: [ScrollItem], _ cellsToReuse: inout [UIView]) -> UIView { + let item: ScrollItem? = index >= 0 && index < items.count ? items[index] : nil + let cell: UIView + if #available(iOS 16.0, *), false { + let c: UITableViewCell = cellsToReuse.isEmpty ? UITableViewCell() : cellsToReuse.removeLast() as! UITableViewCell + if let item { + c.contentConfiguration = UIHostingConfiguration { self.content(index, item) } + .margins(.all, 0) + .minSize(height: 1) // Passing zero will result in system default of 44 points being used + } + cell = c + } else { + let c = cellsToReuse.isEmpty ? HostingCell() : cellsToReuse.removeLast() as! HostingCell + if let item { + c.set(content: self.content(index, item), parent: self) + } + cell = c + } + cell.isHidden = false + cell.backgroundColor = .clear + let size = cell.systemLayoutSizeFitting(CGSizeMake(scrollView.bounds.width, CGFloat.greatestFiniteMagnitude)) + cell.frame.size.width = scrollView.bounds.width + cell.frame.size.height = size.height + return cell + } + + private func updateCell(cell: UIView, _ index: Int, _ items: [ScrollItem]) { + let item = items[index] + if #available(iOS 16.0, *), false { + (cell as! UITableViewCell).contentConfiguration = UIHostingConfiguration { self.content(index, item) } + .margins(.all, 0) + .minSize(height: 1) // Passing zero will result in system default of 44 points being used + } else { + if let cell = cell as? HostingCell { + cell.set(content: self.content(index, item), parent: self) + } else { + fatalError("Unexpected Cell Type for: \(item)") + } + } + let size = cell.systemLayoutSizeFitting(CGSizeMake(scrollView.bounds.width, CGFloat.greatestFiniteMagnitude)) + cell.frame.size.width = scrollView.bounds.width + cell.frame.size.height = size.height + cell.setNeedsLayout() + } + } +} + +class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestureRecognizerDelegate where ScrollItem : Identifiable, ScrollItem: Hashable { + + /// Stores actual state of the scroll view and all elements drawn on the screen + let listState: ListState = ListState() + + /// Just some random big number that will probably be enough to scrolling down and up without reaching the end + var initialOffset: CGFloat = 100000000 + + /// Default item id when no items in the visible items list. Something that will never be in real data + fileprivate static var DEFAULT_ITEM_ID: any Hashable { get { Int64.min } } + + /// Storing an offset that was already used for laying down content to be able to see the difference + var prevProcessedOffset: CGFloat = 0 + + /// When screen is being rotated, it's important to track the view size and adjust scroll offset accordingly because the view doesn't know that the content + /// starts from bottom and ends at top, not vice versa as usual + var oldScreenHeight: CGFloat = 0 + + /// Not 100% correct height of the content since the items loaded lazily and their dimensions are unkown until they are on screen + var estimatedContentHeight: ContentHeight = ContentHeight() + + /// Specify here the value that is small enough to NOT see any weird animation when you scroll to items. Minimum expected item size is ok. Scroll speed depends on it too + var averageItemHeight: CGFloat = 30 + + /// This is used as a multiplier for difference between current index and scrollTo index using [averageItemHeight] as well. Increase it to get faster speed + var scrollStepMultiplier: CGFloat = 0.37 + + /// Adds content padding to top + var insetTop: CGFloat = 100 + + /// Adds content padding to bottom + var insetBottom: CGFloat = 100 + + var scrollToItemIndexDelayed: Int? = nil + + /// The second scroll view that is used only for purpose of displaying scroll bar with made-up content size and scroll offset that is gathered from main scroll view, see [estimatedContentHeight] + let scrollBarView: UIScrollView = UIScrollView(frame: .zero) + + /// Stores views that can be used to hold new content so it will be faster to replace something than to create the whole view from scratch + var cellsToReuse: [UIView] = [] + + /// Enable debug to see hundreds of logs + var debug: Bool = false + + var createCell: (Int, [ScrollItem], inout [UIView]) -> UIView? = { _, _, _ in nil } + var updateCell: (UIView, Int, [ScrollItem]) -> Void = { cell, _, _ in } + + + override init(frame: CGRect) { + super.init(frame: frame) + self.delegate = self + } + + required init?(coder: NSCoder) { fatalError() } + + class ListState: NSObject { + + /// Will be called on every change of the items array, visible items, and scroll position + var onUpdateListener: () -> Void = {} + + /// Items that were used to lay out the screen + var items: [ScrollItem] = [] { + didSet { + onUpdateListener() + } + } + + /// It is equai to the number of [items] + var totalItemsCount: Int { + items.count + } + + /// The items with their positions and other useful information. Only those that are visible on screen + var visibleItems: [EndlessScrollView.VisibleItem] = [] + + /// Index in [items] of the first item on screen. This is intentiallty not derived from visible items because it's is used as a starting point for laying out the screen + var firstVisibleItemIndex: Int = 0 + + /// Unique item id of the first visible item on screen + var firstVisibleItemId: any Hashable = EndlessScrollView.DEFAULT_ITEM_ID + + /// Item offset of the first item on screen. Most of the time it's non-positive but it can be positive as well when a user produce overscroll effect on top/bottom of the scroll view + var firstVisibleItemOffset: CGFloat = -100 + + /// Index of the last visible item on screen + var lastVisibleItemIndex: Int { + visibleItems.last?.index ?? 0 + } + + /// Specifies if visible items cover the whole screen or can cover it (if overscrolled) + var itemsCanCoverScreen: Bool = false + + /// Whether there is a non-animated scroll to item in progress or not + var isScrolling: Bool = false + /// Whether there is an animated scroll to item in progress or not + var isAnimatedScrolling: Bool = false + + override init() { + super.init() + } + } + + class VisibleItem { + let index: Int + let item: ScrollItem + let view: UIView + var offset: CGFloat + + init(index: Int, item: ScrollItem, view: UIView, offset: CGFloat) { + self.index = index + self.item = item + self.view = view + self.offset = offset + } + } + + class ContentHeight { + /// After that you should see overscroll effect. When scroll positon is far from + /// top/bottom items, these values are estimated based on items count multiplied by averageItemHeight or real item height (from visible items). Example: + /// [ 10, 9, 8, 7, (6, 5, 4, 3), 2, 1, 0] - 6, 5, 4, 3 are visible and have know heights but others have unknown height and for them averageItemHeight will be used to calculate the whole content height + var topOffsetY: CGFloat = 0 + var bottomOffsetY: CGFloat = 0 + + var virtualScrollOffsetY: CGFloat = 0 + + /// How much distance were overscolled on top which often means to show sticky scrolling that should scroll back to real position after a users finishes dragging the scrollView + var overscrolledTop: CGFloat = 0 + + /// Adds content padding to bottom and top + var inset: CGFloat = 100 + + /// Estimated height of the contents of scroll view + var height: CGFloat { + get { bottomOffsetY - topOffsetY } + } + + /// Estimated height of the contents of scroll view + distance of overscrolled effect. It's only updated when number of item changes to prevent jumping of scroll bar + var virtualOverscrolledHeight: CGFloat { + get { + bottomOffsetY - topOffsetY + overscrolledTop - inset * 2 + } + } + + func update( + _ contentOffset: CGPoint, + _ listState: ListState, + _ averageItemHeight: CGFloat, + _ updateStaleHeight: Bool + ) { + let lastVisible = listState.visibleItems.last + let firstVisible = listState.visibleItems.first + guard let last = lastVisible, let first = firstVisible else { + topOffsetY = contentOffset.y + bottomOffsetY = contentOffset.y + virtualScrollOffsetY = 0 + overscrolledTop = 0 + return + } + topOffsetY = last.view.frame.origin.y - CGFloat(listState.totalItemsCount - last.index - 1) * averageItemHeight - self.inset + bottomOffsetY = first.view.frame.origin.y + first.view.bounds.height + CGFloat(first.index) * averageItemHeight + self.inset + virtualScrollOffsetY = contentOffset.y - topOffsetY + overscrolledTop = max(0, last.index == listState.totalItemsCount - 1 ? last.view.frame.origin.y - contentOffset.y : 0) + } + } + + var topY: CGFloat { + get { contentOffset.y } + } + + var bottomY: CGFloat { + get { contentOffset.y + bounds.height } + } + + override func layoutSubviews() { + super.layoutSubviews() + if contentSize.height == 0 { + setup() + } + let newScreenHeight = bounds.height + if newScreenHeight != oldScreenHeight && oldScreenHeight != 0 { + contentOffset.y += oldScreenHeight - newScreenHeight + scrollBarView.frame = CGRectMake(frame.width - 10, self.insetTop, 10, frame.height - self.insetTop - self.insetBottom) + } + oldScreenHeight = newScreenHeight + adaptItems(listState.items, false) + if let index = scrollToItemIndexDelayed { + scrollToItem(index) + scrollToItemIndexDelayed = nil + } + } + + private func setup() { + contentSize = CGSizeMake(frame.size.width, initialOffset * 2) + prevProcessedOffset = initialOffset + contentOffset = CGPointMake(0, initialOffset) + + showsVerticalScrollIndicator = false + scrollBarView.showsHorizontalScrollIndicator = false + panGestureRecognizer.delegate = self + addGestureRecognizer(scrollBarView.panGestureRecognizer) + superview!.addSubview(scrollBarView) + } + + func updateItems(_ items: [ScrollItem], _ forceReloadVisible: Bool = false) { + if !Thread.isMainThread { + logger.error("Use main thread to update items") + return + } + if bounds.height == 0 { + self.listState.items = items + // this function requires to have valid bounds and it will be called again once it has them + return + } + adaptItems(items, forceReloadVisible) + snapToContent(animated: false) + } + + /// [forceReloadVisible]: reloads every item that was visible regardless of hashValue changes + private func adaptItems(_ items: [ScrollItem], _ forceReloadVisible: Bool, overridenOffset: CGFloat? = nil) { + let start = Date.now + // special case when everything was removed + if items.isEmpty { + listState.visibleItems.forEach { item in item.view.removeFromSuperview() } + listState.visibleItems = [] + listState.itemsCanCoverScreen = false + listState.firstVisibleItemId = EndlessScrollView.DEFAULT_ITEM_ID + listState.firstVisibleItemIndex = 0 + listState.firstVisibleItemOffset = -insetTop + + estimatedContentHeight.update(contentOffset, listState, averageItemHeight, true) + scrollBarView.contentSize = .zero + scrollBarView.contentOffset = .zero + + prevProcessedOffset = contentOffset.y + // this check is just to prevent didSet listener from firing on the same empty array, no use for this + if !self.listState.items.isEmpty { + self.listState.items = items + } + return + } + + let contentOffsetY = overridenOffset ?? contentOffset.y + + var oldVisible = listState.visibleItems + var newVisible: [VisibleItem] = [] + var visibleItemsHeight: CGFloat = 0 + let offsetsDiff = contentOffsetY - prevProcessedOffset + + var shouldBeFirstVisible = items.firstIndex(where: { item in item.id == listState.firstVisibleItemId as! ScrollItem.ID }) ?? 0 + + var wasFirstVisibleItemOffset = listState.firstVisibleItemOffset + var alreadyChangedIndexWhileScrolling = false + var allowOneMore = false + var nextOffsetY: CGFloat = 0 + var i = shouldBeFirstVisible + // building list of visible items starting from the first one that should be visible + while i >= 0 && i < items.count { + let item = items[i] + let visibleIndex = oldVisible.firstIndex(where: { vis in vis.item.id == item.id }) + let visible: VisibleItem? + if let visibleIndex { + let v = oldVisible.remove(at: visibleIndex) + if forceReloadVisible || v.view.bounds.width != bounds.width || v.item.hashValue != item.hashValue { + let wasHeight = v.view.bounds.height + updateCell(v.view, i, items) + if wasHeight < v.view.bounds.height && i == 0 && shouldBeFirstVisible == i { + v.view.frame.origin.y -= v.view.bounds.height - wasHeight + } + } + visible = v + } else { + visible = nil + } + if shouldBeFirstVisible == i { + if let vis = visible { + + if // there is auto scroll in progress and the first item has a higher offset than bottom part + // of the screen. In order to make scrolling down & up equal in time, we treat this as a sign to + // re-make the first visible item + (listState.isAnimatedScrolling && vis.view.frame.origin.y + vis.view.bounds.height < contentOffsetY + bounds.height) || + // the fist visible item previously is hidden now, remove it and move on + !isVisible(vis.view) { + let newIndex: Int + if listState.isAnimatedScrolling { + // skip many items to make the scrolling take less time + var indexDiff = !alreadyChangedIndexWhileScrolling ? Int(ceil(abs(offsetsDiff / averageItemHeight))) : 0 + // if index was already changed, no need to change it again. Otherwise, the scroll will overscoll and return back animated. Because it means the whole screen was scrolled + alreadyChangedIndexWhileScrolling = true + + indexDiff = offsetsDiff <= 0 ? indexDiff : -indexDiff + newIndex = max(0, min(items.count - 1, i + indexDiff)) + // offset for the first visible item can now be 0 because the previous first visible item doesn't exist anymore + wasFirstVisibleItemOffset = 0 + } else { + // don't skip multiple items if it's manual scrolling gesture + newIndex = i + (offsetsDiff <= 0 ? 1 : -1) + } + shouldBeFirstVisible = newIndex + i = newIndex + + cellsToReuse.append(vis.view) + hideAndRemoveFromSuperviewIfNeeded(vis.view) + continue + } + } + let vis: VisibleItem + if let visible { + vis = VisibleItem(index: i, item: item, view: visible.view, offset: offsetToBottom(visible.view)) + } else { + let cell = createCell(i, items, &cellsToReuse)! + cell.frame.origin.y = bottomY + wasFirstVisibleItemOffset - cell.frame.height + vis = VisibleItem(index: i, item: item, view: cell, offset: offsetToBottom(cell)) + } + if vis.view.superview == nil { + addSubview(vis.view) + } + newVisible.append(vis) + visibleItemsHeight += vis.view.frame.height + nextOffsetY = vis.view.frame.origin.y + } else { + let vis: VisibleItem + if let visible { + vis = VisibleItem(index: i, item: item, view: visible.view, offset: offsetToBottom(visible.view)) + nextOffsetY -= vis.view.frame.height + vis.view.frame.origin.y = nextOffsetY + } else { + let cell = createCell(i, items, &cellsToReuse)! + nextOffsetY -= cell.frame.height + cell.frame.origin.y = nextOffsetY + vis = VisibleItem(index: i, item: item, view: cell, offset: offsetToBottom(cell)) + } + if vis.view.superview == nil { + addSubview(vis.view) + } + newVisible.append(vis) + visibleItemsHeight += vis.view.frame.height + } + if abs(nextOffsetY) < contentOffsetY && !allowOneMore { + break + } else if abs(nextOffsetY) < contentOffsetY { + allowOneMore = false + } + i += 1 + } + if let firstVisible = newVisible.first, firstVisible.view.frame.origin.y + firstVisible.view.frame.height < contentOffsetY + bounds.height, firstVisible.index > 0 { + var offset: CGFloat = firstVisible.view.frame.origin.y + firstVisible.view.frame.height + let index = firstVisible.index + for i in stride(from: index - 1, through: 0, by: -1) { + let item = items[i] + let visibleIndex = oldVisible.firstIndex(where: { vis in vis.item.id == item.id }) + let vis: VisibleItem + if let visibleIndex { + let visible = oldVisible.remove(at: visibleIndex) + visible.view.frame.origin.y = offset + vis = VisibleItem(index: i, item: item, view: visible.view, offset: offsetToBottom(visible.view)) + } else { + let cell = createCell(i, items, &cellsToReuse)! + cell.frame.origin.y = offset + vis = VisibleItem(index: i, item: item, view: cell, offset: offsetToBottom(cell)) + } + if vis.view.superview == nil { + addSubview(vis.view) + } + offset += vis.view.frame.height + newVisible.insert(vis, at: 0) + visibleItemsHeight += vis.view.frame.height + if offset >= contentOffsetY + bounds.height { + break + } + } + } + + // removing already unneeded visible items + oldVisible.forEach { vis in + cellsToReuse.append(vis.view) + hideAndRemoveFromSuperviewIfNeeded(vis.view) + } + let itemsCountChanged = listState.items.count != items.count + prevProcessedOffset = contentOffsetY + + listState.visibleItems = newVisible + // bottom drawing starts from 0 until top visible area at least (bound.height - insetTop) or above top bar (bounds.height). + // For visible items to preserve offset after adding more items having such height is enough + listState.itemsCanCoverScreen = visibleItemsHeight >= bounds.height - insetTop + + listState.firstVisibleItemId = listState.visibleItems.first?.item.id ?? EndlessScrollView.DEFAULT_ITEM_ID + listState.firstVisibleItemIndex = listState.visibleItems.first?.index ?? 0 + listState.firstVisibleItemOffset = listState.visibleItems.first?.offset ?? -insetTop + // updating the items with the last step in order to call listener with fully updated state + listState.items = items + + estimatedContentHeight.update(contentOffset, listState, averageItemHeight, itemsCountChanged) + scrollBarView.contentSize = CGSizeMake(bounds.width, estimatedContentHeight.virtualOverscrolledHeight) + scrollBarView.contentOffset = CGPointMake(0, estimatedContentHeight.virtualScrollOffsetY) + scrollBarView.isHidden = listState.visibleItems.count == listState.items.count && (listState.visibleItems.isEmpty || -listState.firstVisibleItemOffset + (listState.visibleItems.last?.offset ?? 0) + insetTop < bounds.height) + + if debug { + println("time spent \((-start.timeIntervalSinceNow).description.prefix(5).replacingOccurrences(of: "0.000", with: "<0").replacingOccurrences(of: "0.", with: ""))") + } + } + + func setScrollPosition(_ index: Int, _ id: Int64, _ offset: CGFloat = 0) { + listState.firstVisibleItemIndex = index + listState.firstVisibleItemId = id + listState.firstVisibleItemOffset = offset == 0 ? -bounds.height + insetTop + insetBottom : offset + } + + func scrollToItem(_ index: Int, top: Bool = true) { + if index >= listState.items.count || listState.isScrolling || listState.isAnimatedScrolling { + return + } + if bounds.height == 0 || contentSize.height == 0 { + scrollToItemIndexDelayed = index + return + } + listState.isScrolling = true + defer { + listState.isScrolling = false + } + + // just a faster way to set top item as requested index + listState.firstVisibleItemIndex = index + listState.firstVisibleItemId = listState.items[index].id + listState.firstVisibleItemOffset = -bounds.height + insetTop + insetBottom + scrollBarView.flashScrollIndicators() + adaptItems(listState.items, false) + + var adjustedOffset = self.contentOffset.y + var i = 0 + + var upPrev = index > listState.firstVisibleItemIndex + //let firstOrLastIndex = upPrev ? listState.visibleItems.last?.index ?? 0 : listState.firstVisibleItemIndex + //let step: CGFloat = max(0.1, CGFloat(abs(index - firstOrLastIndex)) * scrollStepMultiplier) + + var stepSlowdownMultiplier: CGFloat = 1 + while i < 200 { + let up = index > listState.firstVisibleItemIndex + if upPrev != up { + stepSlowdownMultiplier = stepSlowdownMultiplier * 0.5 + upPrev = up + } + + // these two lines makes scrolling's finish non-linear and NOT overscroll visually when reach target index + let firstOrLastIndex = up ? listState.visibleItems.last?.index ?? 0 : listState.firstVisibleItemIndex + let step: CGFloat = max(0.1, CGFloat(abs(index - firstOrLastIndex)) * scrollStepMultiplier) * stepSlowdownMultiplier + + let offsetToScroll = (up ? -averageItemHeight : averageItemHeight) * step + adjustedOffset += offsetToScroll + if let item = listState.visibleItems.first(where: { $0.index == index }) { + let y = if top { + min(estimatedContentHeight.bottomOffsetY - bounds.height, item.view.frame.origin.y - insetTop) + } else { + max(estimatedContentHeight.topOffsetY - insetTop - insetBottom, item.view.frame.origin.y + item.view.bounds.height - bounds.height + insetBottom) + } + setContentOffset(CGPointMake(contentOffset.x, y), animated: false) + scrollBarView.flashScrollIndicators() + break + } + contentOffset = CGPointMake(contentOffset.x, adjustedOffset) + adaptItems(listState.items, false) + snapToContent(animated: false) + i += 1 + } + adaptItems(listState.items, false) + snapToContent(animated: false) + estimatedContentHeight.update(contentOffset, listState, averageItemHeight, true) + } + + func scrollToItemAnimated(_ index: Int, top: Bool = true) async { + if index >= listState.items.count || listState.isScrolling || listState.isAnimatedScrolling { + return + } + listState.isAnimatedScrolling = true + defer { + listState.isAnimatedScrolling = false + } + var adjustedOffset = self.contentOffset.y + var i = 0 + + var upPrev = index > listState.firstVisibleItemIndex + //let firstOrLastIndex = upPrev ? listState.visibleItems.last?.index ?? 0 : listState.firstVisibleItemIndex + //let step: CGFloat = max(0.1, CGFloat(abs(index - firstOrLastIndex)) * scrollStepMultiplier) + + var stepSlowdownMultiplier: CGFloat = 1 + while i < 200 { + let up = index > listState.firstVisibleItemIndex + if upPrev != up { + stepSlowdownMultiplier = stepSlowdownMultiplier * 0.5 + upPrev = up + } + + // these two lines makes scrolling's finish non-linear and NOT overscroll visually when reach target index + let firstOrLastIndex = up ? listState.visibleItems.last?.index ?? 0 : listState.firstVisibleItemIndex + let step: CGFloat = max(0.1, CGFloat(abs(index - firstOrLastIndex)) * scrollStepMultiplier) * stepSlowdownMultiplier + + //println("Scrolling step \(step) \(stepSlowdownMultiplier) index \(index) \(firstOrLastIndex) \(index - firstOrLastIndex) \(adjustedOffset), up \(up), i \(i)") + + let offsetToScroll = (up ? -averageItemHeight : averageItemHeight) * step + adjustedOffset += offsetToScroll + if let item = listState.visibleItems.first(where: { $0.index == index }) { + let y = if top { + min(estimatedContentHeight.bottomOffsetY - bounds.height, item.view.frame.origin.y - insetTop) + } else { + max(estimatedContentHeight.topOffsetY - insetTop - insetBottom, item.view.frame.origin.y + item.view.bounds.height - bounds.height + insetBottom) + } + setContentOffset(CGPointMake(contentOffset.x, y), animated: true) + scrollBarView.flashScrollIndicators() + break + } + contentOffset = CGPointMake(contentOffset.x, adjustedOffset) + + // skipping unneded relayout if this offset is already processed + if prevProcessedOffset - contentOffset.y != 0 { + adaptItems(listState.items, false) + snapToContent(animated: false) + } + // let UI time to update to see the animated position change + await MainActor.run {} + + i += 1 + } + estimatedContentHeight.update(contentOffset, listState, averageItemHeight, true) + } + + func scrollToBottom() { + scrollToItem(0, top: false) + } + + func scrollToBottomAnimated() { + Task { + await scrollToItemAnimated(0, top: false) + } + } + + func scroll(by: CGFloat, animated: Bool = true) { + setContentOffset(CGPointMake(contentOffset.x, contentOffset.y + by), animated: animated) + } + + func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { + if !listState.items.isEmpty { + scrollToBottomAnimated() + } + return false + } + + private func snapToContent(animated: Bool) { + let topBlankSpace = estimatedContentHeight.height < bounds.height ? bounds.height - estimatedContentHeight.height : 0 + if topY < estimatedContentHeight.topOffsetY - topBlankSpace { + setContentOffset(CGPointMake(0, estimatedContentHeight.topOffsetY - topBlankSpace), animated: animated) + } else if bottomY > estimatedContentHeight.bottomOffsetY { + setContentOffset(CGPointMake(0, estimatedContentHeight.bottomOffsetY - bounds.height), animated: animated) + } + } + + func offsetToBottom(_ view: UIView) -> CGFloat { + bottomY - (view.frame.origin.y + view.frame.height) + } + + /// If I try to .removeFromSuperview() right when I need to remove the view, it is possible to crash the app when the view was hidden in result of + /// pressing Hide in menu on top of the revealed item within the group. So at that point the item should still be attached to the view + func hideAndRemoveFromSuperviewIfNeeded(_ view: UIView) { + if view.isHidden { + // already passed this function + return + } + (view as? ReusableView)?.prepareForReuse() + view.isHidden = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + if view.isHidden { view.removeFromSuperview() } + } + } + + /// Synchronizing both scrollViews + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + true + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + snapToContent(animated: true) + } + } + + override var contentOffset: CGPoint { + get { super.contentOffset } + set { + var newOffset = newValue + let topBlankSpace = estimatedContentHeight.height < bounds.height ? bounds.height - estimatedContentHeight.height : 0 + if contentOffset.y > 0 && newOffset.y < estimatedContentHeight.topOffsetY - topBlankSpace && contentOffset.y > newOffset.y { + if !isDecelerating { + newOffset.y = min(contentOffset.y, newOffset.y + abs(newOffset.y - estimatedContentHeight.topOffsetY + topBlankSpace) / 1.8) + } else { + DispatchQueue.main.async { + self.setContentOffset(newValue, animated: false) + self.snapToContent(animated: true) + } + } + } else if contentOffset.y > 0 && newOffset.y + bounds.height > estimatedContentHeight.bottomOffsetY && contentOffset.y < newOffset.y { + if !isDecelerating { + newOffset.y = max(contentOffset.y, newOffset.y - abs(newOffset.y + bounds.height - estimatedContentHeight.bottomOffsetY) / 1.8) + } else { + DispatchQueue.main.async { + self.setContentOffset(newValue, animated: false) + self.snapToContent(animated: true) + } + } + } + super.contentOffset = newOffset + } + } + + private func stopScrolling() { + let offsetYToStopAt = if abs(contentOffset.y - estimatedContentHeight.topOffsetY) < abs(bottomY - estimatedContentHeight.bottomOffsetY) { + estimatedContentHeight.topOffsetY + } else { + estimatedContentHeight.bottomOffsetY - bounds.height + } + setContentOffset(CGPointMake(contentOffset.x, offsetYToStopAt), animated: false) + } + + func isVisible(_ view: UIView) -> Bool { + if view.superview == nil { + return false + } + return view.frame.intersects(CGRectMake(0, contentOffset.y, bounds.width, bounds.height)) + } +} + +private func println(_ text: String) { + print("\(Date.now.timeIntervalSince1970): \(text)") +} diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 189ab95494..7cd543af10 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -78,7 +78,12 @@ struct AddGroupMembersViewCommon: View { let count = selectedContacts.count Section { if creatingGroup { - groupPreferencesButton($groupInfo, true) + GroupPreferencesButton( + groupInfo: $groupInfo, + preferences: groupInfo.fullGroupPreferences, + currentPreferences: groupInfo.fullGroupPreferences, + creatingGroup: true + ) } rolePicker() inviteMembersButton() @@ -105,8 +110,10 @@ struct AddGroupMembersViewCommon: View { .padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let members = s == "" ? membersToAdd : membersToAdd.filter { $0.chatViewName.localizedLowercase.contains(s) } - ForEach(members) { contact in - contactCheckView(contact) + ForEach(members + [dummyContact]) { contact in + if contact.contactId != dummyContact.contactId { + contactCheckView(contact) + } } } } @@ -130,12 +137,21 @@ struct AddGroupMembersViewCommon: View { .modifier(ThemedBackground(grouped: true)) } + // Resolves keyboard losing focus bug in iOS16 and iOS17, + // when there are no items inside `ForEach(memebers)` loop + private let dummyContact: Contact = { + var dummy = Contact.sampleData + dummy.contactId = -1 + return dummy + }() + private func inviteMembersButton() -> some View { - Button { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Invite to group" : "Invite to chat" + return Button { inviteMembers() } label: { HStack { - Text("Invite to group") + Text(label) Image(systemName: "checkmark") } } @@ -159,10 +175,8 @@ struct AddGroupMembersViewCommon: View { private func rolePicker() -> some View { Picker("New member role", selection: $selectedRole) { - ForEach(GroupMemberRole.allCases) { role in - if role <= groupInfo.membership.memberRole && role != .author { - Text(role.text) - } + ForEach(GroupMemberRole.supportedRoles.filter({ $0 <= groupInfo.membership.memberRole })) { role in + Text(role.text) } } .frame(height: 36) @@ -221,6 +235,7 @@ func searchFieldView(text: Binding, focussed: FocusState.Binding, .focused(focussed) .foregroundColor(onBackgroundColor) .frame(maxWidth: .infinity) + .autocorrectionDisabled(true) Image(systemName: "xmark.circle.fill") .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 9385633060..15749b0761 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -18,8 +18,10 @@ struct GroupChatInfoView: View { @ObservedObject var chat: Chat @Binding var groupInfo: GroupInfo var onSearch: () -> Void + @State var localAlias: String + @FocusState private var aliasTextFieldFocused: Bool @State private var alert: GroupChatInfoViewAlert? = nil - @State private var groupLink: String? + @State private var groupLink: CreatedConnLink? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var groupLinkNavLinkActive: Bool = false @State private var addMembersNavLinkActive: Bool = false @@ -27,6 +29,7 @@ struct GroupChatInfoView: View { @State private var connectionCode: String? @State private var sendReceipts = SendReceipts.userDefault(true) @State private var sendReceiptsUserDefault = true + @State private var progressIndicator = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @State private var searchText: String = "" @FocusState private var searchFocussed @@ -67,94 +70,112 @@ struct GroupChatInfoView: View { .filter { m in let status = m.wrapped.memberStatus; return status != .memLeft && status != .memRemoved } .sorted { $0.wrapped.memberRole > $1.wrapped.memberRole } - List { - groupInfoHeader() - .listRowBackground(Color.clear) - .padding(.bottom, 18) - - infoActionButtons() - .padding(.horizontal) - .frame(maxWidth: .infinity) - .frame(height: infoViewActionButtonHeight) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - - Section { - if groupInfo.canEdit { - editGroupButton() - } - if groupInfo.groupProfile.description != nil || groupInfo.canEdit { - addOrEditWelcomeMessage() - } - groupPreferencesButton($groupInfo) - if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { - sendReceiptsOption() - } else { - sendReceiptsOptionDisabled() - } - NavigationLink { - ChatWallpaperEditorSheet(chat: chat) - } label: { - Label("Chat theme", systemImage: "photo") - } - } header: { - Text("") - } footer: { - Text("Only group owners can change group preferences.") - .foregroundColor(theme.colors.secondary) - } - - Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) { - if groupInfo.canAddMembers { - groupLinkButton() - if (chat.chatInfo.incognito) { - Label("Invite members", systemImage: "plus") - .foregroundColor(Color(uiColor: .tertiaryLabel)) - .onTapGesture { alert = .cantInviteIncognitoAlert } - } else { - addMembersButton() + ZStack { + List { + groupInfoHeader() + .listRowBackground(Color.clear) + + localAliasTextEdit() + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .padding(.bottom, 18) + + infoActionButtons() + .padding(.horizontal) + .frame(maxWidth: .infinity) + .frame(height: infoViewActionButtonHeight) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + + Section { + if groupInfo.isOwner && groupInfo.businessChat == nil { + editGroupButton() } + if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) { + addOrEditWelcomeMessage() + } + GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences) + if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { + sendReceiptsOption() + } else { + sendReceiptsOptionDisabled() + } + + NavigationLink { + ChatWallpaperEditorSheet(chat: chat) + } label: { + Label("Chat theme", systemImage: "photo") + } + } header: { + Text("") + } footer: { + let label: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Only group owners can change group preferences." + : "Only chat owners can change preferences." + ) + Text(label) + .foregroundColor(theme.colors.secondary) } - if members.count > 8 { + + Section { + ChatTTLOption(chat: chat, progressIndicator: $progressIndicator) + } footer: { + Text("Delete chat messages from your device.") + } + + Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) { + if groupInfo.canAddMembers { + if groupInfo.businessChat == nil { + groupLinkButton() + } + if (chat.chatInfo.incognito) { + Label("Invite members", systemImage: "plus") + .foregroundColor(Color(uiColor: .tertiaryLabel)) + .onTapGesture { alert = .cantInviteIncognitoAlert } + } else { + addMembersButton() + } + } searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary) .padding(.leading, 8) + let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + let filteredMembers = s == "" + ? members + : members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) + ForEach(filteredMembers) { member in + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, alert: $alert) + } } - let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase - let filteredMembers = s == "" ? members : members.filter { $0.wrapped.chatViewName.localizedLowercase.contains(s) } - MemberRowView(groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) - ForEach(filteredMembers) { member in - ZStack { - NavigationLink { - memberInfoView(member) - } label: { - EmptyView() - } - .opacity(0) - MemberRowView(groupInfo: groupInfo, groupMember: member, alert: $alert) + + Section { + clearChatButton() + if groupInfo.canDelete { + deleteGroupButton() + } + if groupInfo.membership.memberCurrent { + leaveGroupButton() + } + } + + if developerTools { + Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { + infoRow("Local name", chat.chatInfo.localDisplayName) + infoRow("Database ID", "\(chat.chatInfo.apiId)") } } } - - Section { - clearChatButton() - if groupInfo.canDelete { - deleteGroupButton() - } - if groupInfo.membership.memberCurrent { - leaveGroupButton() - } - } - - if developerTools { - Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { - infoRow("Local name", chat.chatInfo.localDisplayName) - infoRow("Database ID", "\(chat.chatInfo.apiId)") - } + .modifier(ThemedBackground(grouped: true)) + .navigationBarHidden(true) + .disabled(progressIndicator) + .opacity(progressIndicator ? 0.6 : 1) + + if progressIndicator { + ProgressView().scaleEffect(2) } } - .modifier(ThemedBackground(grouped: true)) - .navigationBarHidden(true) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .alert(item: $alert) { alertItem in @@ -193,7 +214,7 @@ struct GroupChatInfoView: View { ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.top, 12) .padding() - Text(cInfo.displayName) + Text(cInfo.groupInfo?.groupProfile.displayName ?? cInfo.displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(4) @@ -208,6 +229,37 @@ struct GroupChatInfoView: View { .frame(maxWidth: .infinity, alignment: .center) } + private func localAliasTextEdit() -> some View { + TextField("Set chat name…", text: $localAlias) + .disableAutocorrection(true) + .focused($aliasTextFieldFocused) + .submitLabel(.done) + .onChange(of: aliasTextFieldFocused) { focused in + if !focused { + setGroupAlias() + } + } + .onSubmit { + setGroupAlias() + } + .multilineTextAlignment(.center) + .foregroundColor(theme.colors.secondary) + } + + private func setGroupAlias() { + Task { + do { + if let gInfo = try await apiSetGroupAlias(groupId: chat.chatInfo.apiId, localAlias: localAlias) { + await MainActor.run { + chatModel.updateGroup(gInfo) + } + } + } catch { + logger.error("setGroupAlias error: \(responseError(error))") + } + } + } + func infoActionButtons() -> some View { GeometryReader { g in let buttonWidth = g.size.width / 4 @@ -216,7 +268,9 @@ struct GroupChatInfoView: View { if groupInfo.canAddMembers { addMembersActionButton(width: buttonWidth) } - muteButton(width: buttonWidth) + if let nextNtfMode = chat.chatInfo.nextNtfMode { + muteButton(width: buttonWidth, nextNtfMode: nextNtfMode) + } } .frame(maxWidth: .infinity, alignment: .center) } @@ -230,9 +284,9 @@ struct GroupChatInfoView: View { .disabled(!groupInfo.ready || chat.chatItems.isEmpty) } - @ViewBuilder private func addMembersActionButton(width: CGFloat) -> some View { - if chat.chatInfo.incognito { - ZStack { + private func addMembersActionButton(width: CGFloat) -> some View { + ZStack { + if chat.chatInfo.incognito { InfoViewButton(image: "link.badge.plus", title: "invite", width: width) { groupLinkNavLinkActive = true } @@ -244,10 +298,7 @@ struct GroupChatInfoView: View { } .frame(width: 1, height: 1) .hidden() - } - .disabled(!groupInfo.ready) - } else { - ZStack { + } else { InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) { addMembersNavLinkActive = true } @@ -260,26 +311,31 @@ struct GroupChatInfoView: View { .frame(width: 1, height: 1) .hidden() } - .disabled(!groupInfo.ready) } + .disabled(!groupInfo.ready) } - private func muteButton(width: CGFloat) -> some View { - InfoViewButton( - image: chat.chatInfo.ntfsEnabled ? "speaker.slash.fill" : "speaker.wave.2.fill", - title: chat.chatInfo.ntfsEnabled ? "mute" : "unmute", + private func muteButton(width: CGFloat, nextNtfMode: MsgFilter) -> some View { + return InfoViewButton( + image: nextNtfMode.iconFilled, + title: "\(nextNtfMode.text(mentions: true))", width: width ) { - toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) + toggleNotifications(chat, enableNtfs: nextNtfMode) } .disabled(!groupInfo.ready) } private func addMembersButton() -> some View { - NavigationLink { + let label: LocalizedStringKey = switch groupInfo.businessChat?.chatType { + case .customer: "Add team members" + case .business: "Add friends" + case .none: "Invite members" + } + return NavigationLink { addMembersDestinationView() } label: { - Label("Invite members", systemImage: "plus") + Label(label, systemImage: "plus") } } @@ -288,16 +344,13 @@ struct GroupChatInfoView: View { .onAppear { searchFocussed = false Task { - let groupMembers = await apiListMembers(groupInfo.groupId) - await MainActor.run { - chatModel.groupMembers = groupMembers.map { GMember.init($0) } - chatModel.populateGroupMembersIndexes() - } + await chatModel.loadGroupMembers(groupInfo) } } } private struct MemberRowView: View { + var chat: Chat var groupInfo: GroupInfo @ObservedObject var groupMember: GMember @EnvironmentObject var theme: AppTheme @@ -306,7 +359,7 @@ struct GroupChatInfoView: View { var body: some View { let member = groupMember.wrapped - let v = HStack{ + let v1 = HStack{ MemberProfileImage(member, size: 38) .padding(.trailing, 2) // TODO server connection status @@ -322,7 +375,21 @@ struct GroupChatInfoView: View { Spacer() memberInfo(member) } - + + let v = ZStack { + if user { + v1 + } else { + NavigationLink { + memberInfoView() + } label: { + EmptyView() + } + .opacity(0) + v1 + } + } + if user { v } else if groupInfo.membership.memberRole >= .admin { @@ -347,6 +414,11 @@ struct GroupChatInfoView: View { } } + private func memberInfoView() -> some View { + GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) + .navigationBarHidden(false) + } + private func memberConnStatus(_ member: GroupMember) -> LocalizedStringKey { if member.activeConn?.connDisabled ?? false { return "disabled" @@ -418,7 +490,7 @@ struct GroupChatInfoView: View { } private var memberVerifiedShield: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .font(.caption) .baselineOffset(2) .kerning(-2) @@ -426,11 +498,6 @@ struct GroupChatInfoView: View { } } - private func memberInfoView(_ groupMember: GMember) -> some View { - GroupMemberInfoView(groupInfo: groupInfo, groupMember: groupMember) - .navigationBarHidden(false) - } - private func groupLinkButton() -> some View { NavigationLink { groupLinkDestinationView() @@ -487,11 +554,12 @@ struct GroupChatInfoView: View { } } - private func deleteGroupButton() -> some View { + @ViewBuilder private func deleteGroupButton() -> some View { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group" : "Delete chat" Button(role: .destructive) { alert = .deleteGroupAlert } label: { - Label("Delete group", systemImage: "trash") + Label(label, systemImage: "trash") .foregroundColor(Color.red) } } @@ -506,19 +574,21 @@ struct GroupChatInfoView: View { } private func leaveGroupButton() -> some View { - Button(role: .destructive) { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group" : "Leave chat" + return Button(role: .destructive) { alert = .leaveGroupAlert } label: { - Label("Leave group", systemImage: "rectangle.portrait.and.arrow.right") + Label(label, systemImage: "rectangle.portrait.and.arrow.right") .foregroundColor(Color.red) } } // TODO reuse this and clearChatAlert with ChatInfoView private func deleteGroupAlert() -> Alert { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?" return Alert( - title: Text("Delete group?"), - message: deleteGroupAlertMessage(), + title: Text(label), + message: deleteGroupAlertMessage(groupInfo), primaryButton: .destructive(Text("Delete")) { Task { do { @@ -537,10 +607,6 @@ struct GroupChatInfoView: View { ) } - private func deleteGroupAlertMessage() -> Text { - groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") - } - private func clearChatAlert() -> Alert { Alert( title: Text("Clear conversation?"), @@ -556,9 +622,15 @@ struct GroupChatInfoView: View { } private func leaveGroupAlert() -> Alert { - Alert( - title: Text("Leave group?"), - message: Text("You will stop receiving messages from this group. Chat history will be preserved."), + let titleLabel: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group?" : "Leave chat?" + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "You will stop receiving messages from this group. Chat history will be preserved." + : "You will stop receiving messages from this chat. Chat history will be preserved." + ) + return Alert( + title: Text(titleLabel), + message: Text(messageLabel), primaryButton: .destructive(Text("Leave")) { Task { await leaveGroup(chat.chatInfo.apiId) @@ -602,18 +674,25 @@ struct GroupChatInfoView: View { } private func removeMemberAlert(_ mem: GroupMember) -> Alert { - Alert( + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Member will be removed from group - this cannot be undone!" + : "Member will be removed from chat - this cannot be undone!" + ) + return Alert( title: Text("Remove member?"), - message: Text("Member will be removed from group - this cannot be undone!"), + message: Text(messageLabel), primaryButton: .destructive(Text("Remove")) { Task { do { - let updatedMember = try await apiRemoveMember(groupInfo.groupId, mem.groupMemberId) + let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId]) await MainActor.run { - _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + updatedMembers.forEach { updatedMember in + _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + } } } catch let error { - logger.error("apiRemoveMember error: \(responseError(error))") + logger.error("apiRemoveMembers error: \(responseError(error))") let a = getErrorAlert(error, "Error removing member") alert = .error(title: a.title, error: a.message) } @@ -624,26 +703,80 @@ struct GroupChatInfoView: View { } } -func groupPreferencesButton(_ groupInfo: Binding, _ creatingGroup: Bool = false) -> some View { - NavigationLink { - GroupPreferencesView( - groupInfo: groupInfo, - preferences: groupInfo.wrappedValue.fullGroupPreferences, - currentPreferences: groupInfo.wrappedValue.fullGroupPreferences, - creatingGroup: creatingGroup - ) - .navigationBarTitle("Group preferences") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - if creatingGroup { - Text("Set group preferences") - } else { - Label("Group preferences", systemImage: "switch.2") +func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { + groupInfo.businessChat == nil ? ( + groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") + ) : ( + groupInfo.membership.memberCurrent ? Text("Chat will be deleted for all members - this cannot be undone!") : Text("Chat will be deleted for you - this cannot be undone!") + ) +} + +struct GroupPreferencesButton: View { + @Binding var groupInfo: GroupInfo + @State var preferences: FullGroupPreferences + @State var currentPreferences: FullGroupPreferences + var creatingGroup: Bool = false + + private var label: LocalizedStringKey { + groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences" + } + + var body: some View { + NavigationLink { + GroupPreferencesView( + groupInfo: $groupInfo, + preferences: $preferences, + currentPreferences: currentPreferences, + creatingGroup: creatingGroup, + savePreferences: savePreferences + ) + .navigationBarTitle(label) + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + .onDisappear { + let saveText = NSLocalizedString( + creatingGroup ? "Save" : "Save and notify group members", + comment: "alert button" + ) + + if groupInfo.fullGroupPreferences != preferences { + showAlert( + title: NSLocalizedString("Save preferences?", comment: "alert title"), + buttonTitle: saveText, + buttonAction: { savePreferences() }, + cancelButton: true + ) + } + } + } label: { + if creatingGroup { + Text("Set group preferences") + } else { + Label(label, systemImage: "switch.2") + } } } + + private func savePreferences() { + Task { + do { + var gp = groupInfo.groupProfile + gp.groupPreferences = toGroupPreferences(preferences) + let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp) + await MainActor.run { + groupInfo = gInfo + ChatModel.shared.updateGroup(gInfo) + currentPreferences = preferences + } + } catch { + logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))") + } + } + } + } + func cantInviteIncognitoAlert() -> Alert { Alert( title: Text("Can't invite contacts!"), @@ -663,7 +796,8 @@ struct GroupChatInfoView_Previews: PreviewProvider { GroupChatInfoView( chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: Binding.constant(GroupInfo.sampleData), - onSearch: {} + onSearch: {}, + localAlias: "" ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index 39288e2d52..a11c073a42 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -10,12 +10,14 @@ import SwiftUI import SimpleXChat struct GroupLinkView: View { + @EnvironmentObject var theme: AppTheme var groupId: Int64 - @Binding var groupLink: String? + @Binding var groupLink: CreatedConnLink? @Binding var groupLinkMemberRole: GroupMemberRole var showTitle: Bool = false var creatingGroup: Bool = false var linkCreatedCb: (() -> Void)? = nil + @State private var showShortLink = true @State private var creatingLink = false @State private var alert: GroupLinkAlert? @State private var shouldCreate = true @@ -69,10 +71,10 @@ struct GroupLinkView: View { } } .frame(height: 36) - SimpleXLinkQRCode(uri: groupLink) - .id("simplex-qrcode-view-for-\(groupLink)") + SimpleXCreatedLinkQRCode(link: groupLink, short: $showShortLink) + .id("simplex-qrcode-view-for-\(groupLink.simplexChatUri(short: showShortLink))") Button { - showShareSheet(items: [simplexChatLink(groupLink)]) + showShareSheet(items: [groupLink.simplexChatUri(short: showShortLink)]) } label: { Label("Share link", systemImage: "square.and.arrow.up") } @@ -93,6 +95,10 @@ struct GroupLinkView: View { .frame(maxWidth: .infinity) } } + } header: { + if let groupLink, groupLink.connShortLink != nil { + ToggleShortLinkHeader(text: Text(""), link: groupLink, short: $showShortLink) + } } .alert(item: $alert) { alert in switch alert { @@ -158,8 +164,8 @@ struct GroupLinkView: View { struct GroupLinkView_Previews: PreviewProvider { static var previews: some View { - @State var groupLink: String? = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" - @State var noGroupLink: String? = nil + @State var groupLink: CreatedConnLink? = CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil) + @State var noGroupLink: CreatedConnLink? = nil return Group { GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member)) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index ddf3b8e4b9..79ad242366 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -14,10 +14,15 @@ struct GroupMemberInfoView: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction @State var groupInfo: GroupInfo + @ObservedObject var chat: Chat @ObservedObject var groupMember: GMember var navigation: Bool = false @State private var connectionStats: ConnectionStats? = nil @State private var connectionCode: String? = nil + @State private var connectionLoaded: Bool = false + @State private var knownContactChat: Chat? = nil + @State private var knownContact: Contact? = nil + @State private var knownContactConnectionStats: ConnectionStats? = nil @State private var newRole: GroupMemberRole = .member @State private var alert: GroupMemberInfoViewAlert? @State private var sheet: PlanAndConnectActionSheet? @@ -94,128 +99,149 @@ struct GroupMemberInfoView: View { .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - if member.memberActive { - Section { - if let code = connectionCode { verifyCodeButton(code) } - if let connStats = connectionStats, - connStats.ratchetSyncAllowed { - synchronizeConnectionButton() - } - // } else if developerTools { - // synchronizeConnectionButtonForce() - // } - } - } + if connectionLoaded { - if let contactLink = member.contactLink { - Section { - SimpleXLinkQRCode(uri: contactLink) - Button { - showShareSheet(items: [simplexChatLink(contactLink)]) - } label: { - Label("Share address", systemImage: "square.and.arrow.up") + if member.memberActive { + Section { + if let code = connectionCode { verifyCodeButton(code) } + if let connStats = connectionStats, + connStats.ratchetSyncAllowed { + synchronizeConnectionButton() + } + // } else if developerTools { + // synchronizeConnectionButtonForce() + // } } - if let contactId = member.memberContactId { - if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { + } + + if let contactLink = member.contactLink { + Section { + SimpleXLinkQRCode(uri: contactLink) + Button { + showShareSheet(items: [simplexChatLink(contactLink)]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + if member.memberContactId != nil { + if knownContactChat == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { + connectViaAddressButton(contactLink) + } + } else { connectViaAddressButton(contactLink) } - } else { - connectViaAddressButton(contactLink) + } header: { + Text("Address") + .foregroundColor(theme.colors.secondary) + } footer: { + Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") + .foregroundColor(theme.colors.secondary) } - } header: { - Text("Address") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") - .foregroundColor(theme.colors.secondary) } - } - Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { - infoRow("Group", groupInfo.displayName) + Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Group" : "Chat" + infoRow(label, groupInfo.displayName) - if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { - Picker("Change role", selection: $newRole) { - ForEach(roles) { role in - Text(role.text) + if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { + Picker("Change role", selection: $newRole) { + ForEach(roles) { role in + Text(role.text) + } } + .frame(height: 36) + } else { + infoRow("Role", member.memberRole.text) } - .frame(height: 36) - } else { - infoRow("Role", member.memberRole.text) } - } - if let connStats = connectionStats { - Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { - // TODO network connection status - Button("Change receiving address") { - alert = .switchAddressAlert - } - .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } - || connStats.ratchetSyncSendProhibited - ) - if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { - Button("Abort changing address") { - alert = .abortSwitchAddressAlert + if let connStats = connectionStats { + Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { + // TODO network connection status + Button("Change receiving address") { + alert = .switchAddressAlert } .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } - || connStats.ratchetSyncSendProhibited + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } + || !member.sendMsgEnabled ) + if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { + Button("Abort changing address") { + alert = .abortSwitchAddressAlert + } + .disabled( + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + || !member.sendMsgEnabled + ) + } + smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) + smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } - smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) - smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } - } - if groupInfo.membership.memberRole >= .admin { - adminDestructiveSection(member) - } else { - nonAdminBlockSection(member) - } + if groupInfo.membership.memberRole >= .admin { + adminDestructiveSection(member) + } else { + nonAdminBlockSection(member) + } - if developerTools { - Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { - infoRow("Local name", member.localDisplayName) - infoRow("Database ID", "\(member.groupMemberId)") - if let conn = member.activeConn { - let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) - infoRow("Connection", connLevelDesc) - } - Button ("Debug delivery") { - Task { - do { - let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) - await MainActor.run { alert = .queueInfo(info: info) } - } catch let e { - logger.error("apiContactQueueInfo error: \(responseError(e))") - let a = getErrorAlert(e, "Error") - await MainActor.run { alert = .error(title: a.title, error: a.message) } + if developerTools { + Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { + infoRow("Local name", member.localDisplayName) + infoRow("Database ID", "\(member.groupMemberId)") + if let conn = member.activeConn { + let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) + infoRow("Connection", connLevelDesc) + } + Button ("Debug delivery") { + Task { + do { + let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) + await MainActor.run { alert = .queueInfo(info: info) } + } catch let e { + logger.error("apiContactQueueInfo error: \(responseError(e))") + let a = getErrorAlert(e, "Error") + await MainActor.run { alert = .error(title: a.title, error: a.message) } + } } } } } + } } .navigationBarHidden(true) - .onAppear { + .task { if #unavailable(iOS 16) { // this condition prevents re-setting picker if !justOpened { return } } justOpened = false - DispatchQueue.main.async { - newRole = member.memberRole - do { - let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) - let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) + newRole = member.memberRole + do { + let (_, stats) = try await apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) + let (mem, code) = member.memberActive ? try await apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) + await MainActor.run { _ = chatModel.upsertGroupMember(groupInfo, mem) connectionStats = stats connectionCode = code + connectionLoaded = true + } + } catch let error { + await MainActor.run { + connectionLoaded = true + } + logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") + } + if let contactId = member.memberContactId, let (contactChat, contact) = knownDirectChat(contactId) { + knownContactChat = contactChat + knownContact = contact + do { + let (stats, _) = try await apiContactInfo(contactChat.chatInfo.apiId) + await MainActor.run { + knownContactConnectionStats = stats + } } catch let error { - logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") + logger.error("apiContactInfo error: \(responseError(error))") } } } @@ -251,6 +277,11 @@ struct GroupMemberInfoView: View { ProgressView().scaleEffect(2) } } + .onChange(of: chat.chatInfo) { c in + if case let .group(gI) = chat.chatInfo { + groupInfo = gI + } + } .modifier(ThemedBackground(grouped: true)) } @@ -258,15 +289,15 @@ struct GroupMemberInfoView: View { GeometryReader { g in let buttonWidth = g.size.width / 4 HStack(alignment: .center, spacing: 8) { - if let contactId = member.memberContactId, let (chat, contact) = knownDirectChat(contactId) { + if let chat = knownContactChat, let contact = knownContact { knownDirectChatButton(chat, width: buttonWidth) - AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } - VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } + AudioCallButton(chat: chat, contact: contact, connectionStats: $knownContactConnectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } + VideoButton(chat: chat, contact: contact, connectionStats: $knownContactConnectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } } else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { if let contactId = member.memberContactId { newDirectChatButton(contactId, width: buttonWidth) - } else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false { - createMemberContactButton(width: buttonWidth) + } else if member.versionRange.maxVersion >= CREATE_MEMBER_CONTACT_VERSION { + createMemberContactButton(member, width: buttonWidth) } InfoViewButton(image: "phone.fill", title: "call", disabledLook: true, width: buttonWidth) { showSendMessageToEnableCallsAlert() } @@ -296,10 +327,15 @@ struct GroupMemberInfoView: View { } func showDirectMessagesProhibitedAlert(_ title: LocalizedStringKey) { + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Direct messages between members are prohibited." + : "Direct messages between members are prohibited in this chat." + ) alert = .someAlert(alert: SomeAlert( alert: mkAlert( title: title, - message: "Direct messages between members are prohibited in this group." + message: messageLabel ), id: "can't message member, direct messages prohibited" )) @@ -330,41 +366,76 @@ struct GroupMemberInfoView: View { func newDirectChatButton(_ contactId: Int64, width: CGFloat) -> some View { InfoViewButton(image: "message.fill", title: "message", width: width) { Task { - do { - let chat = try await apiGetChat(type: .direct, id: contactId) - chatModel.addChat(chat) - ItemsModel.shared.loadOpenChat(chat.id) { - dismissAllSheets(animated: true) - } - } catch let error { - logger.error("openDirectChatButton apiGetChat error: \(responseError(error))") + ItemsModel.shared.loadOpenChat("@\(contactId)") { + dismissAllSheets(animated: true) } } } } - func createMemberContactButton(width: CGFloat) -> some View { - InfoViewButton(image: "message.fill", title: "message", width: width) { - progressIndicator = true - Task { - do { - let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId) - await MainActor.run { - progressIndicator = false - chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) - ItemsModel.shared.loadOpenChat(memberContact.id) { - dismissAllSheets(animated: true) + func createMemberContactButton(_ member: GroupMember, width: CGFloat) -> some View { + InfoViewButton( + image: "message.fill", + title: "message", + disabledLook: + !( + member.sendMsgEnabled || + (member.activeConn?.connectionStats?.ratchetSyncAllowed ?? false) + ), + width: width + ) { + if member.sendMsgEnabled { + progressIndicator = true + Task { + do { + let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId) + await MainActor.run { + progressIndicator = false + chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) + ItemsModel.shared.loadOpenChat(memberContact.id) { + dismissAllSheets(animated: true) + } + NetworkModel.shared.setContactNetworkStatus(memberContact, .connected) + } + } catch let error { + logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") + let a = getErrorAlert(error, "Error creating member contact") + await MainActor.run { + progressIndicator = false + alert = .error(title: a.title, error: a.message) } - NetworkModel.shared.setContactNetworkStatus(memberContact, .connected) - } - } catch let error { - logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") - let a = getErrorAlert(error, "Error creating member contact") - await MainActor.run { - progressIndicator = false - alert = .error(title: a.title, error: a.message) } } + } else if let connStats = connectionStats { + if connStats.ratchetSyncAllowed { + alert = .someAlert(alert: SomeAlert( + alert: Alert( + title: Text("Fix connection?"), + message: Text("Connection requires encryption renegotiation."), + primaryButton: .default(Text("Fix")) { + syncMemberConnection(force: false) + }, + secondaryButton: .cancel() + ), + id: "can't message member, fix connection" + )) + } else if connStats.ratchetSyncInProgress { + alert = .someAlert(alert: SomeAlert( + alert: mkAlert( + title: "Can't message member", + message: "Encryption renegotiation in progress." + ), + id: "can't message member, encryption renegotiation in progress" + )) + } else { + alert = .someAlert(alert: SomeAlert( + alert: mkAlert( + title: "Can't message member", + message: "Connection not ready." + ), + id: "can't message member, connection not ready" + )) + } } } } @@ -379,7 +450,7 @@ struct GroupMemberInfoView: View { Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) - + Text(" ") + + textSpace + Text(mem.displayName) .font(.largeTitle) ) @@ -528,19 +599,26 @@ struct GroupMemberInfoView: View { } private func removeMemberAlert(_ mem: GroupMember) -> Alert { - Alert( + let label: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Member will be removed from group - this cannot be undone!" + : "Member will be removed from chat - this cannot be undone!" + ) + return Alert( title: Text("Remove member?"), - message: Text("Member will be removed from group - this cannot be undone!"), + message: Text(label), primaryButton: .destructive(Text("Remove")) { Task { do { - let updatedMember = try await apiRemoveMember(groupInfo.groupId, mem.groupMemberId) + let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId]) await MainActor.run { - _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + updatedMembers.forEach { updatedMember in + _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + } dismiss() } } catch let error { - logger.error("apiRemoveMember error: \(responseError(error))") + logger.error("apiRemoveMembers error: \(responseError(error))") let a = getErrorAlert(error, "Error removing member") alert = .error(title: a.title, error: a.message) } @@ -553,18 +631,28 @@ struct GroupMemberInfoView: View { private func changeMemberRoleAlert(_ mem: GroupMember) -> Alert { Alert( title: Text("Change member role?"), - message: mem.memberCurrent ? Text("Member role will be changed to \"\(newRole.text)\". All group members will be notified.") : Text("Member role will be changed to \"\(newRole.text)\". The member will receive a new invitation."), + message: ( + mem.memberCurrent + ? ( + groupInfo.businessChat == nil + ? Text("Member role will be changed to \"\(newRole.text)\". All group members will be notified.") + : Text("Member role will be changed to \"\(newRole.text)\". All chat members will be notified.") + ) + : Text("Member role will be changed to \"\(newRole.text)\". The member will receive a new invitation.") + ), primaryButton: .default(Text("Change")) { Task { do { - let updatedMember = try await apiMemberRole(groupInfo.groupId, mem.groupMemberId, newRole) + let updatedMembers = try await apiMembersRole(groupInfo.groupId, [mem.groupMemberId], newRole) await MainActor.run { - _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + updatedMembers.forEach { updatedMember in + _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + } } - + } catch let error { newRole = mem.memberRole - logger.error("apiMemberRole error: \(responseError(error))") + logger.error("apiMembersRole error: \(responseError(error))") let a = getErrorAlert(error, "Error changing role") alert = .error(title: a.title, error: a.message) } @@ -716,12 +804,14 @@ func unblockForAllAlert(_ gInfo: GroupInfo, _ mem: GroupMember) -> Alert { func blockMemberForAll(_ gInfo: GroupInfo, _ member: GroupMember, _ blocked: Bool) { Task { do { - let updatedMember = try await apiBlockMemberForAll(gInfo.groupId, member.groupMemberId, blocked) + let updatedMembers = try await apiBlockMembersForAll(gInfo.groupId, [member.groupMemberId], blocked) await MainActor.run { - _ = ChatModel.shared.upsertGroupMember(gInfo, updatedMember) + updatedMembers.forEach { updatedMember in + _ = ChatModel.shared.upsertGroupMember(gInfo, updatedMember) + } } } catch let error { - logger.error("apiBlockMemberForAll error: \(responseError(error))") + logger.error("apiBlockMembersForAll error: \(responseError(error))") } } } @@ -730,6 +820,7 @@ struct GroupMemberInfoView_Previews: PreviewProvider { static var previews: some View { GroupMemberInfoView( groupInfo: GroupInfo.sampleData, + chat: Chat.sampleData, groupMember: GMember.sampleData ) } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift new file mode 100644 index 0000000000..9bb4a0cc35 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift @@ -0,0 +1,249 @@ +// +// GroupMentions.swift +// SimpleX (iOS) +// +// Created by Diogo Cunha on 30/01/2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +let MENTION_START: Character = "@" +let QUOTE: Character = "'" +let MEMBER_ROW_SIZE: CGFloat = 60 +let MAX_VISIBLE_MEMBER_ROWS: CGFloat = 4.8 + +struct GroupMentionsView: View { + @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme + var groupInfo: GroupInfo + @Binding var composeState: ComposeState + @Binding var selectedRange: NSRange + @Binding var keyboardVisible: Bool + + @State private var isVisible = false + @State private var currentMessage: String = "" + @State private var mentionName: String = "" + @State private var mentionRange: NSRange? + @State private var mentionMemberId: String? + @State private var sortedMembers: [GMember] = [] + + var body: some View { + ZStack(alignment: .bottom) { + if isVisible { + let filtered = filteredMembers() + if filtered.count > 0 { + Color.white.opacity(0.01) + .edgesIgnoringSafeArea(.all) + .onTapGesture { + isVisible = false + } + VStack(spacing: 0) { + Spacer() + Divider() + let scroll = ScrollView { + LazyVStack(spacing: 0) { + ForEach(Array(filtered.enumerated()), id: \.element.wrapped.groupMemberId) { index, member in + let mentioned = mentionMemberId == member.wrapped.memberId + let disabled = composeState.mentions.count >= MAX_NUMBER_OF_MENTIONS && !mentioned + ZStack(alignment: .bottom) { + memberRowView(member.wrapped, mentioned) + .contentShape(Rectangle()) + .disabled(disabled) + .opacity(disabled ? 0.6 : 1) + .onTapGesture { + memberSelected(member) + } + .padding(.horizontal) + .frame(height: MEMBER_ROW_SIZE) + + Divider() + .padding(.leading) + .padding(.leading, 48) + } + } + } + } + .frame(maxHeight: MEMBER_ROW_SIZE * min(MAX_VISIBLE_MEMBER_ROWS, CGFloat(filtered.count))) + .background(Color(UIColor.systemBackground)) + + if #available(iOS 16.0, *) { + scroll.scrollDismissesKeyboard(.never) + } else { + scroll + } + } + } + } + } + .onChange(of: composeState.parsedMessage) { parsedMsg in + currentMessage = composeState.message + messageChanged(currentMessage, parsedMsg, selectedRange) + } + .onChange(of: selectedRange) { r in + // This condition is needed to prevent messageChanged called twice, + // because composeState.formattedText triggers later when message changes. + // The condition is only true if position changed without text change + if currentMessage == composeState.message { + messageChanged(currentMessage, composeState.parsedMessage, r) + } + } + .onAppear { + currentMessage = composeState.message + } + } + + private func filteredMembers() -> [GMember] { + let s = mentionName.lowercased() + return s.isEmpty + ? sortedMembers + : sortedMembers.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } + } + + private func messageChanged(_ msg: String, _ parsedMsg: [FormattedText], _ range: NSRange) { + removeUnusedMentions(parsedMsg) + if let (ft, r) = selectedMarkdown(parsedMsg, range) { + switch ft.format { + case let .mention(name): + isVisible = true + mentionName = name + mentionRange = r + mentionMemberId = composeState.mentions[name]?.memberId + if !m.membersLoaded { + Task { + await m.loadGroupMembers(groupInfo) + sortMembers() + } + } + return + case .none: () // + let pos = range.location + if range.length == 0, let (at, atRange) = getCharacter(msg, pos - 1), at == "@" { + let prevChar = getCharacter(msg, pos - 2)?.char + if prevChar == nil || prevChar == " " || prevChar == "\n" { + isVisible = true + mentionName = "" + mentionRange = atRange + mentionMemberId = nil + Task { + await m.loadGroupMembers(groupInfo) + sortMembers() + } + return + } + } + default: () + } + } + closeMemberList() + } + + private func sortMembers() { + sortedMembers = m.groupMembers.filter({ m in + let status = m.wrapped.memberStatus + return status != .memLeft && status != .memRemoved && status != .memInvited + }) + .sorted { $0.wrapped.memberRole > $1.wrapped.memberRole } + } + + private func removeUnusedMentions(_ parsedMsg: [FormattedText]) { + let usedMentions: Set = Set(parsedMsg.compactMap { ft in + if case let .mention(name) = ft.format { name } else { nil } + }) + if usedMentions.count < composeState.mentions.count { + composeState = composeState.copy(mentions: composeState.mentions.filter({ usedMentions.contains($0.key) })) + } + } + + private func getCharacter(_ s: String, _ pos: Int) -> (char: String.SubSequence, range: NSRange)? { + if pos < 0 || pos >= s.count { return nil } + let r = NSRange(location: pos, length: 1) + return if let range = Range(r, in: s) { + (s[range], r) + } else { + nil + } + } + + private func selectedMarkdown(_ parsedMsg: [FormattedText], _ range: NSRange) -> (FormattedText, NSRange)? { + if parsedMsg.isEmpty { return nil } + var i = 0 + var pos: Int = 0 + while i < parsedMsg.count && pos + parsedMsg[i].text.count < range.location { + pos += parsedMsg[i].text.count + i += 1 + } + // the second condition will be true when two markdowns are selected + return i >= parsedMsg.count || range.location + range.length > pos + parsedMsg[i].text.count + ? nil + : (parsedMsg[i], NSRange(location: pos, length: parsedMsg[i].text.count)) + } + + private func memberSelected(_ member: GMember) { + if let range = mentionRange, mentionMemberId == nil || mentionMemberId != member.wrapped.memberId { + addMemberMention(member, range) + } + } + + private func addMemberMention(_ member: GMember, _ r: NSRange) { + guard let range = Range(r, in: composeState.message) else { return } + var mentions = composeState.mentions + var newName: String + if let mm = mentions.first(where: { $0.value.memberId == member.wrapped.memberId }) { + newName = mm.key + } else { + newName = composeState.mentionMemberName(member.wrapped.memberProfile.displayName) + } + mentions[newName] = CIMention(groupMember: member.wrapped) + var msgMention = newName.contains(" ") || newName.last?.isPunctuation == true + ? "@'\(newName)'" + : "@\(newName)" + var newPos = r.location + msgMention.count + let newMsgLength = composeState.message.count + msgMention.count - r.length + print(newPos) + print(newMsgLength) + if newPos == newMsgLength { + msgMention += " " + newPos += 1 + } + composeState = composeState.copy( + message: composeState.message.replacingCharacters(in: range, with: msgMention), + mentions: mentions + ) + selectedRange = NSRange(location: newPos, length: 0) + closeMemberList() + keyboardVisible = true + } + + private func closeMemberList() { + isVisible = false + mentionName = "" + mentionRange = nil + mentionMemberId = nil + } + + private func memberRowView(_ member: GroupMember, _ mentioned: Bool) -> some View { + return HStack{ + MemberProfileImage(member, size: 38) + .padding(.trailing, 2) + VStack(alignment: .leading) { + let t = Text(member.localAliasAndFullName).foregroundColor(member.memberIncognito ? .indigo : theme.colors.onBackground) + (member.verified ? memberVerifiedShield() + t : t) + .lineLimit(1) + } + Spacer() + if mentioned { + Image(systemName: "checkmark") + } + } + + func memberVerifiedShield() -> Text { + (Text(Image(systemName: "checkmark.shield")) + textSpace) + .font(.caption) + .baselineOffset(2) + .kerning(-2) + .foregroundColor(theme.colors.secondary) + } + } +} diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 2b0d05375b..ed39c401ce 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -20,9 +20,10 @@ struct GroupPreferencesView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var groupInfo: GroupInfo - @State var preferences: FullGroupPreferences - @State var currentPreferences: FullGroupPreferences + @Binding var preferences: FullGroupPreferences + var currentPreferences: FullGroupPreferences let creatingGroup: Bool + let savePreferences: () -> Void @State private var showSaveDialogue = false var body: some View { @@ -36,9 +37,10 @@ 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(.history, $preferences.history.enable) - if groupInfo.canEdit { + if groupInfo.isOwner { Section { Button("Reset") { preferences = currentPreferences } Button(saveText) { savePreferences() } @@ -68,7 +70,10 @@ struct GroupPreferencesView: View { savePreferences() dismiss() } - Button("Exit without saving") { dismiss() } + Button("Exit without saving") { + preferences = currentPreferences + dismiss() + } } } @@ -77,7 +82,7 @@ struct GroupPreferencesView: View { let color: Color = enableFeature.wrappedValue == .on ? .green : theme.colors.secondary let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon let timedOn = feature == .timedMessages && enableFeature.wrappedValue == .on - if groupInfo.canEdit { + if groupInfo.isOwner { let enable = Binding( get: { enableFeature.wrappedValue == .on }, set: { on, _ in enableFeature.wrappedValue = on ? .on : .off } @@ -85,6 +90,7 @@ struct GroupPreferencesView: View { settingsRow(icon, color: color) { Toggle(feature.text, isOn: enable) } + .disabled(feature == .reports) // remove in 6.4 if timedOn { DropdownCustomTimePicker( selection: $preferences.timedMessages.ttl, @@ -123,7 +129,7 @@ struct GroupPreferencesView: View { } } } footer: { - Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit)) + Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner)) .foregroundColor(theme.colors.secondary) } .onChange(of: enableFeature.wrappedValue) { enabled in @@ -132,32 +138,16 @@ struct GroupPreferencesView: View { } } } - - private func savePreferences() { - Task { - do { - var gp = groupInfo.groupProfile - gp.groupPreferences = toGroupPreferences(preferences) - let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp) - await MainActor.run { - groupInfo = gInfo - chatModel.updateGroup(gInfo) - currentPreferences = preferences - } - } catch { - logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))") - } - } - } } struct GroupPreferencesView_Previews: PreviewProvider { static var previews: some View { GroupPreferencesView( groupInfo: Binding.constant(GroupInfo.sampleData), - preferences: FullGroupPreferences.sampleData, + preferences: Binding.constant(FullGroupPreferences.sampleData), currentPreferences: FullGroupPreferences.sampleData, - creatingGroup: false + creatingGroup: false, + savePreferences: {} ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift index 18cc3f4d80..1617edd11f 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift @@ -110,10 +110,13 @@ struct GroupProfileView: View { } } .onChange(of: chosenImage) { image in - if let image = image { - groupProfile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) - } else { - groupProfile.image = nil + Task { + let resized: String? = if let image { + await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + nil + } + await MainActor.run { groupProfile.image = resized } } } .onAppear { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 9a9002f9dc..97bff70efb 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -18,12 +18,13 @@ struct GroupWelcomeView: View { @State private var editMode = true @FocusState private var keyboardVisible: Bool @State private var showSaveDialog = false + @State private var showSecrets: Set = [] let maxByteCount = 1200 var body: some View { VStack { - if groupInfo.canEdit { + if groupInfo.isOwner && groupInfo.businessChat == nil { editorView() .modifier(BackButton(disabled: Binding.constant(false)) { if welcomeTextUnchanged() { @@ -58,7 +59,8 @@ struct GroupWelcomeView: View { } private func textPreview() -> some View { - messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil, showSecrets: false, secondaryColor: theme.colors.secondary) + let r = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: showSecrets, backgroundColor: UIColor(theme.colors.background)) + return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) .frame(minHeight: 130, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift deleted file mode 100644 index 94d160e1b4..0000000000 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ /dev/null @@ -1,316 +0,0 @@ -// -// ReverseList.swift -// SimpleX (iOS) -// -// Created by Levitating Pineapple on 11/06/2024. -// Copyright © 2024 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import Combine - -/// A List, which displays it's items in reverse order - from bottom to top -struct ReverseList: UIViewControllerRepresentable { - let items: Array - - @Binding var scrollState: ReverseListScrollModel.State - - /// Closure, that returns user interface for a given item - let content: (Item) -> Content - - let loadPage: () -> Void - - func makeUIViewController(context: Context) -> Controller { - Controller(representer: self) - } - - func updateUIViewController(_ controller: Controller, context: Context) { - if case let .scrollingTo(destination) = scrollState, !items.isEmpty { - switch destination { - case .nextPage: - controller.scrollToNextPage() - case let .item(id): - controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom) - case .bottom: - controller.scroll(to: 0, position: .top) - } - } else { - controller.update(items: items) - } - } - - /// Controller, which hosts SwiftUI cells - class Controller: UITableViewController { - private enum Section { case main } - private let representer: ReverseList - private var dataSource: UITableViewDiffableDataSource! - private var itemCount: Int = 0 - private var bag = Set() - - init(representer: ReverseList) { - self.representer = representer - super.init(style: .plain) - - // 1. Style - tableView = InvertedTableView() - tableView.separatorStyle = .none - tableView.transform = .verticalFlip - tableView.backgroundColor = .clear - - // 2. Register cells - if #available(iOS 16.0, *) { - tableView.register( - UITableViewCell.self, - forCellReuseIdentifier: cellReuseId - ) - } else { - tableView.register( - HostingCell.self, - forCellReuseIdentifier: cellReuseId - ) - } - - // 3. Configure data source - self.dataSource = UITableViewDiffableDataSource( - tableView: tableView - ) { (tableView, indexPath, item) -> UITableViewCell? in - if indexPath.item > self.itemCount - 8, self.itemCount > 8 { - self.representer.loadPage() - } - let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) - if #available(iOS 16.0, *) { - cell.contentConfiguration = UIHostingConfiguration { self.representer.content(item) } - .margins(.all, 0) - .minSize(height: 1) // Passing zero will result in system default of 44 points being used - } else { - if let cell = cell as? HostingCell { - cell.set(content: self.representer.content(item), parent: self) - } else { - fatalError("Unexpected Cell Type for: \(item)") - } - } - cell.transform = .verticalFlip - cell.selectionStyle = .none - cell.backgroundColor = .clear - return cell - } - - // 4. External state changes will require manual layout updates - NotificationCenter.default - .addObserver( - self, - selector: #selector(updateLayout), - name: notificationName, - object: nil - ) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { fatalError() } - - deinit { NotificationCenter.default.removeObserver(self) } - - @objc private func updateLayout() { - if #available(iOS 16.0, *) { - tableView.setNeedsLayout() - tableView.layoutIfNeeded() - } else { - tableView.reloadData() - } - } - - /// Hides keyboard, when user begins to scroll. - /// Equivalent to `.scrollDismissesKeyboard(.immediately)` - override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - UIApplication.shared - .sendAction( - #selector(UIResponder.resignFirstResponder), - to: nil, - from: nil, - for: nil - ) - NotificationCenter.default.post(name: .chatViewWillBeginScrolling, object: nil) - } - - override func viewDidAppear(_ animated: Bool) { - tableView.clipsToBounds = false - parent?.viewIfLoaded?.clipsToBounds = false - } - - /// Scrolls up - func scrollToNextPage() { - tableView.setContentOffset( - CGPoint( - x: tableView.contentOffset.x, - y: tableView.contentOffset.y + tableView.bounds.height - ), - animated: true - ) - Task { representer.scrollState = .atDestination } - } - - /// Scrolls to Item at index path - /// - Parameter indexPath: Item to scroll to - will scroll to beginning of the list, if `nil` - func scroll(to index: Int?, position: UITableView.ScrollPosition) { - var animated = false - if #available(iOS 16.0, *) { - animated = true - } - if let index, tableView.numberOfRows(inSection: 0) != 0 { - tableView.scrollToRow( - at: IndexPath(row: index, section: 0), - at: position, - animated: animated - ) - } else { - tableView.setContentOffset( - CGPoint(x: .zero, y: -InvertedTableView.inset), - animated: animated - ) - } - Task { representer.scrollState = .atDestination } - } - - func update(items: Array) { - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems(items) - dataSource.defaultRowAnimation = .none - dataSource.apply( - snapshot, - animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1 - ) - // Sets content offset on initial load - if itemCount == 0 { - tableView.setContentOffset( - CGPoint(x: 0, y: -InvertedTableView.inset), - animated: false - ) - } - itemCount = items.count - } - } - - /// `UIHostingConfiguration` back-port for iOS14 and iOS15 - /// Implemented as a `UITableViewCell` that wraps and manages a generic `UIHostingController` - private final class HostingCell: UITableViewCell { - private let hostingController = UIHostingController(rootView: nil) - - /// Updates content of the cell - /// For reference: https://noahgilmore.com/blog/swiftui-self-sizing-cells/ - func set(content: Hosted, parent: UIViewController) { - hostingController.view.backgroundColor = .clear - hostingController.rootView = content - if let hostingView = hostingController.view { - hostingView.invalidateIntrinsicContentSize() - if hostingController.parent != parent { parent.addChild(hostingController) } - if !contentView.subviews.contains(hostingController.view) { - contentView.addSubview(hostingController.view) - hostingView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - hostingView.leadingAnchor - .constraint(equalTo: contentView.leadingAnchor), - hostingView.trailingAnchor - .constraint(equalTo: contentView.trailingAnchor), - hostingView.topAnchor - .constraint(equalTo: contentView.topAnchor), - hostingView.bottomAnchor - .constraint(equalTo: contentView.bottomAnchor) - ]) - } - if hostingController.parent != parent { hostingController.didMove(toParent: parent) } - } else { - fatalError("Hosting View not loaded \(hostingController)") - } - } - - override func prepareForReuse() { - super.prepareForReuse() - hostingController.rootView = nil - } - } -} - -/// Manages ``ReverseList`` scrolling -class ReverseListScrollModel: ObservableObject { - /// Represents Scroll State of ``ReverseList`` - enum State: Equatable { - enum Destination: Equatable { - case nextPage - case item(Item.ID) - case bottom - } - - case scrollingTo(Destination) - case atDestination - } - - @Published var state: State = .atDestination - - func scrollToNextPage() { - state = .scrollingTo(.nextPage) - } - - func scrollToBottom() { - state = .scrollingTo(.bottom) - } - - func scrollToItem(id: Item.ID) { - state = .scrollingTo(.item(id)) - } -} - -fileprivate let cellReuseId = "hostingCell" - -fileprivate let notificationName = NSNotification.Name(rawValue: "reverseListNeedsLayout") - -fileprivate extension CGAffineTransform { - /// Transform that vertically flips the view, preserving it's location - static let verticalFlip = CGAffineTransform(scaleX: 1, y: -1) -} - -extension NotificationCenter { - static func postReverseListNeedsLayout() { - NotificationCenter.default.post( - name: notificationName, - object: nil - ) - } -} - -/// Disable animation on iOS 15 -func withConditionalAnimation( - _ animation: Animation? = .default, - _ body: () throws -> Result -) rethrows -> Result { - if #available(iOS 16.0, *) { - try withAnimation(animation, body) - } else { - try body() - } -} - -class InvertedTableView: UITableView { - static let inset = CGFloat(100) - - static let insets = UIEdgeInsets( - top: inset, - left: .zero, - bottom: inset, - right: .zero - ) - - override var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior { - get { .never } - set { } - } - - override var contentInset: UIEdgeInsets { - get { Self.insets } - set { } - } - - override var adjustedContentInset: UIEdgeInsets { - Self.insets - } -} diff --git a/apps/ios/Shared/Views/Chat/ScrollViewCells.swift b/apps/ios/Shared/Views/Chat/ScrollViewCells.swift new file mode 100644 index 0000000000..d062627d5b --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ScrollViewCells.swift @@ -0,0 +1,52 @@ +// +// ScrollViewCells.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 27.01.2025. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +protocol ReusableView { + func prepareForReuse() +} + +/// `UIHostingConfiguration` back-port for iOS14 and iOS15 +/// Implemented as a `UIView` that wraps and manages a generic `UIHostingController` +final class HostingCell: UIView, ReusableView { + private let hostingController = UIHostingController(rootView: nil) + + /// Updates content of the cell + /// For reference: https://noahgilmore.com/blog/swiftui-self-sizing-cells/ + func set(content: Hosted, parent: UIViewController) { + hostingController.view.backgroundColor = .clear + hostingController.rootView = content + if let hostingView = hostingController.view { + hostingView.invalidateIntrinsicContentSize() + if hostingController.parent != parent { parent.addChild(hostingController) } + if !subviews.contains(hostingController.view) { + addSubview(hostingController.view) + hostingView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + hostingView.leadingAnchor + .constraint(equalTo: leadingAnchor), + hostingView.trailingAnchor + .constraint(equalTo: trailingAnchor), + hostingView.topAnchor + .constraint(equalTo: topAnchor), + hostingView.bottomAnchor + .constraint(equalTo: bottomAnchor) + ]) + } + if hostingController.parent != parent { hostingController.didMove(toParent: parent) } + } else { + fatalError("Hosting View not loaded \(hostingController)") + } + } + + func prepareForReuse() { + //super.prepareForReuse() + hostingController.rootView = nil + } +} diff --git a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift index 87bc73a60e..85d6b279c5 100644 --- a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift +++ b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift @@ -30,15 +30,22 @@ struct SelectedItemsBottomToolbar: View { var chatInfo: ChatInfo // Bool - delete for everyone is possible var deleteItems: (Bool) -> Void + var archiveItems: () -> Void var moderateItems: () -> Void //var shareItems: () -> Void + var forwardItems: () -> Void @State var deleteEnabled: Bool = false @State var deleteForEveryoneEnabled: Bool = false + @State var canArchiveReports: Bool = false + @State var canModerate: Bool = false @State var moderateEnabled: Bool = false - @State var allButtonsDisabled = false + @State var forwardEnabled: Bool = false + + @State var deleteCountProhibited = false + @State var forwardCountProhibited = false var body: some View { VStack(spacing: 0) { @@ -46,14 +53,19 @@ struct SelectedItemsBottomToolbar: View { HStack(alignment: .center) { Button { - deleteItems(deleteForEveryoneEnabled) + if canArchiveReports { + archiveItems() + } else { + deleteItems(deleteForEveryoneEnabled) + } } label: { Image(systemName: "trash") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!deleteEnabled || allButtonsDisabled ? theme.colors.secondary: .red) + .foregroundColor(!deleteEnabled || deleteCountProhibited ? theme.colors.secondary: .red) } - .disabled(!deleteEnabled || allButtonsDisabled) + .disabled(!deleteEnabled || deleteCountProhibited) Spacer() Button { @@ -61,24 +73,24 @@ struct SelectedItemsBottomToolbar: View { } label: { Image(systemName: "flag") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!moderateEnabled || allButtonsDisabled ? theme.colors.secondary : .red) + .foregroundColor(!moderateEnabled || deleteCountProhibited ? theme.colors.secondary : .red) } - .disabled(!moderateEnabled || allButtonsDisabled) + .disabled(!moderateEnabled || deleteCountProhibited) .opacity(canModerate ? 1 : 0) - Spacer() Button { - //shareItems() + forwardItems() } label: { - Image(systemName: "square.and.arrow.up") + Image(systemName: "arrowshape.turn.up.forward") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(allButtonsDisabled ? theme.colors.secondary : theme.colors.primary) + .foregroundColor(!forwardEnabled || forwardCountProhibited ? theme.colors.secondary : theme.colors.primary) } - .disabled(allButtonsDisabled) - .opacity(0) + .disabled(!forwardEnabled || forwardCountProhibited) } .frame(maxHeight: .infinity) .padding([.leading, .trailing], 12) @@ -101,20 +113,28 @@ struct SelectedItemsBottomToolbar: View { private func recheckItems(_ chatInfo: ChatInfo, _ chatItems: [ChatItem], _ selectedItems: Set?) { let count = selectedItems?.count ?? 0 - allButtonsDisabled = count == 0 || count > 20 + deleteCountProhibited = count == 0 || count > 200 + forwardCountProhibited = count == 0 || count > 20 canModerate = possibleToModerate(chatInfo) + let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info) = chatInfo { + info + } else { + nil + } if let selected = selectedItems { let me: Bool let onlyOwnGroupItems: Bool - (deleteEnabled, deleteForEveryoneEnabled, me, onlyOwnGroupItems, selectedChatItems) = chatItems.reduce((true, true, true, true, [])) { (r, ci) in + (deleteEnabled, deleteForEveryoneEnabled, canArchiveReports, me, onlyOwnGroupItems, forwardEnabled, selectedChatItems) = chatItems.reduce((true, true, true, true, true, true, [])) { (r, ci) in if selected.contains(ci.id) { - var (de, dee, me, onlyOwnGroupItems, sel) = r + var (de, dee, ar, me, onlyOwnGroupItems, fe, sel) = r de = de && ci.canBeDeletedForSelf - dee = dee && ci.meta.deletable && !ci.localNote - onlyOwnGroupItems = onlyOwnGroupItems && ci.chatDir == .groupSnd - me = me && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil + dee = dee && ci.meta.deletable && !ci.localNote && !ci.isReport + ar = ar && ci.isActiveReport && ci.chatDir != .groupSnd && groupInfo != nil && groupInfo!.membership.memberRole >= .moderator + onlyOwnGroupItems = onlyOwnGroupItems && ci.chatDir == .groupSnd && !ci.isReport + me = me && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil && !ci.isReport + fe = fe && ci.content.msgContent != nil && ci.meta.itemDeleted == nil && !ci.isLiveDummy && !ci.isReport sel.insert(ci.id) // we are collecting new selected items here to account for any changes in chat items list - return (de, dee, me, onlyOwnGroupItems, sel) + return (de, dee, ar, me, onlyOwnGroupItems, fe, sel) } else { return r } diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index c84cdb0b97..7abab33177 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -10,7 +10,7 @@ import SwiftUI struct ChatHelp: View { @EnvironmentObject var chatModel: ChatModel - @Binding var showSettings: Bool + let dismissSettingsSheet: DismissAction var body: some View { ScrollView { chatHelp() } @@ -23,7 +23,7 @@ struct ChatHelp: View { VStack(alignment: .leading, spacing: 0) { Text("To ask any questions and to receive updates:") Button("connect to SimpleX Chat developers.") { - showSettings = false + dismissSettingsSheet() DispatchQueue.main.async { UIApplication.shared.open(simplexTeamURL) } @@ -42,7 +42,8 @@ struct ChatHelp: View { Text("above, then choose:") } - Text("**Add contact**: to create a new invitation link, or connect via a link you received.") + Text("**Create 1-time link**: to create and share a new invitation link.") + Text("**Scan / Paste link**: to connect via a link you received.") Text("**Create group**: to create a new group.") } .padding(.top, 24) @@ -61,8 +62,9 @@ struct ChatHelp: View { } struct ChatHelp_Previews: PreviewProvider { + @Environment(\.dismiss) static var mockDismiss + static var previews: some View { - @State var showSettings = false - return ChatHelp(showSettings: $showSettings) + ChatHelp(dismissSettingsSheet: mockDismiss) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index d2a93b9bd1..81d78fbadd 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -43,9 +43,11 @@ func dynamicSize(_ font: DynamicTypeSize) -> DynamicSizes { struct ChatListNavLink: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @EnvironmentObject var chatTagsModel: ChatTagsModel @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = false @ObservedObject var chat: Chat + @Binding var parentSheet: SomeSheet? @State private var showContactRequestDialog = false @State private var showJoinGroupDialog = false @State private var showContactConnectionInfo = false @@ -85,13 +87,14 @@ struct ChatListNavLink: View { progressByTimeout = false } } + .actionSheet(item: $actionSheet) { $0.actionSheet } } - @ViewBuilder private func contactNavLink(_ contact: Contact) -> some View { + private func contactNavLink(_ contact: Contact) -> some View { Group { if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { deleteContactDialog( @@ -118,12 +121,14 @@ struct ChatListNavLink: View { selection: $chatModel.chatId, label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) } ) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() toggleNtfsButton(chat: chat) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { + tagChatButton(chat) if !chat.chatItems.isEmpty { clearChatButton() } @@ -141,15 +146,13 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: dynamicRowHeight) } } .alert(item: $alert) { $0.alert } - .actionSheet(item: $actionSheet) { $0.actionSheet } .sheet(item: $sheet) { if #available(iOS 16.0, *) { $0.content - .presentationDetents([.fraction(0.4)]) + .presentationDetents([.fraction($0.fraction)]) } else { $0.content } @@ -160,7 +163,7 @@ struct ChatListNavLink: View { switch (groupInfo.membership.memberStatus) { case .memInvited: ChatPreviewView(chat: chat, progressByTimeout: $progressByTimeout) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { joinGroupButton() if groupInfo.canDelete { @@ -180,11 +183,12 @@ struct ChatListNavLink: View { .disabled(inProgress) case .memAccepted: ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .onTapGesture { AlertManager.shared.showAlert(groupInvitationAcceptedAlert()) } .swipeActions(edge: .trailing) { + tagChatButton(chat) if (groupInfo.membership.memberCurrent) { leaveGroupChatButton(groupInfo) } @@ -199,34 +203,54 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !groupInfo.ready ) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() toggleNtfsButton(chat: chat) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { - if !chat.chatItems.isEmpty { + tagChatButton(chat) + let showReportsButton = chat.chatStats.reportsCount > 0 && groupInfo.membership.memberRole >= .moderator + let showClearButton = !chat.chatItems.isEmpty + let showDeleteGroup = groupInfo.canDelete + let showLeaveGroup = groupInfo.membership.memberCurrent + let totalNumberOfButtons = 1 + (showReportsButton ? 1 : 0) + (showClearButton ? 1 : 0) + (showDeleteGroup ? 1 : 0) + (showLeaveGroup ? 1 : 0) + + if showClearButton && totalNumberOfButtons <= 3 { clearChatButton() } - if (groupInfo.membership.memberCurrent) { + + if showReportsButton && totalNumberOfButtons <= 3 { + archiveAllReportsButton() + } + + if showLeaveGroup { leaveGroupChatButton(groupInfo) } - if groupInfo.canDelete { + + if showDeleteGroup && totalNumberOfButtons <= 3 { deleteGroupChatButton(groupInfo) + } else if totalNumberOfButtons > 3 { + if showDeleteGroup && !groupInfo.membership.memberActive { + deleteGroupChatButton(groupInfo) + moreOptionsButton(false, chat, groupInfo) + } else { + moreOptionsButton(true, chat, groupInfo) + } } } } } - @ViewBuilder private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View { + private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View { NavLinkPlain( chatId: chat.chatInfo.id, selection: $chatModel.chatId, label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !noteFolder.ready ) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() } @@ -287,14 +311,22 @@ struct ChatListNavLink: View { } @ViewBuilder private func toggleNtfsButton(chat: Chat) -> some View { - Button { - toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) - } label: { - if chat.chatInfo.ntfsEnabled { - SwipeLabel(NSLocalizedString("Mute", comment: "swipe action"), systemImage: "speaker.slash.fill", inverted: oneHandUI) - } else { - SwipeLabel(NSLocalizedString("Unmute", comment: "swipe action"), systemImage: "speaker.wave.2.fill", inverted: oneHandUI) + if let nextMode = chat.chatInfo.nextNtfMode { + Button { + toggleNotifications(chat, enableNtfs: nextMode) + } label: { + SwipeLabel(nextMode.text(mentions: chat.chatInfo.hasMentions), systemImage: nextMode.iconFilled, inverted: oneHandUI) } + } else { + EmptyView() + } + } + + private func archiveAllReportsButton() -> some View { + Button { + AlertManager.shared.showAlert(archiveAllReportsAlert()) + } label: { + SwipeLabel(NSLocalizedString("Archive reports", comment: "swipe action"), systemImage: "archivebox", inverted: oneHandUI) } } @@ -306,7 +338,72 @@ struct ChatListNavLink: View { } .tint(Color.orange) } + + private func tagChatButton(_ chat: Chat) -> some View { + Button { + setTagChatSheet(chat) + } label: { + SwipeLabel(NSLocalizedString("List", comment: "swipe action"), systemImage: "tag.fill", inverted: oneHandUI) + } + .tint(.mint) + } + + private func setTagChatSheet(_ chat: Chat) { + let screenHeight = UIScreen.main.bounds.height + let reservedSpace: Double = 4 * 44 // 2 for padding, 1 for "Create list" and another for extra tag + let tagsSpace = Double(max(chatTagsModel.userTags.count, 3)) * 44 + let fraction = min((reservedSpace + tagsSpace) / screenHeight, 0.62) + + parentSheet = SomeSheet( + content: { + AnyView( + NavigationView { + if chatTagsModel.userTags.isEmpty { + TagListEditor(chat: chat) + } else { + TagListView(chat: chat) + } + } + ) + }, + id: "lists sheet", + fraction: fraction + ) + } + + private func moreOptionsButton(_ canShowGroupDelete: Bool, _ chat: Chat, _ groupInfo: GroupInfo?) -> some View { + Button { + var buttons: [Alert.Button] = [] + buttons.append(.default(Text("Clear")) { + AlertManager.shared.showAlert(clearChatAlert()) + }) + if let groupInfo, chat.chatStats.reportsCount > 0 && groupInfo.membership.memberRole >= .moderator && groupInfo.ready { + buttons.append(.default(Text("Archive reports")) { + AlertManager.shared.showAlert(archiveAllReportsAlert()) + }) + } + + if canShowGroupDelete, let gi = groupInfo, gi.canDelete { + buttons.append(.destructive(Text("Delete")) { + AlertManager.shared.showAlert(deleteGroupAlert(gi)) + }) + } + + buttons.append(.cancel()) + + actionSheet = SomeActionSheet( + actionSheet: ActionSheet( + title: canShowGroupDelete ? Text("Clear or delete group?") : Text("Clear group?"), + buttons: buttons + ), + id: "other options" + ) + } label: { + SwipeLabel(NSLocalizedString("More", comment: "swipe action"), systemImage: "ellipsis", inverted: oneHandUI) + } + } + private func clearNoteFolderButton() -> some View { Button { AlertManager.shared.showAlert(clearNoteFolderAlert()) @@ -336,6 +433,7 @@ struct ChatListNavLink: View { private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View { ContactRequestView(contactRequest: contactRequest, chat: chat) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } @@ -354,7 +452,6 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: dynamicRowHeight) .contentShape(Rectangle()) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { @@ -366,6 +463,7 @@ struct ChatListNavLink: View { private func contactConnectionNavLink(_ contactConnection: PendingContactConnection) -> some View { ContactConnectionView(chat: chat) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { AlertManager.shared.showAlert(deleteContactConnectionAlert(contactConnection) { a in @@ -383,7 +481,6 @@ struct ChatListNavLink: View { } .tint(theme.colors.primary) } - .frame(height: dynamicRowHeight) .appSheet(isPresented: $showContactConnectionInfo) { Group { if case let .contactConnection(contactConnection) = chat.chatInfo { @@ -404,8 +501,9 @@ struct ChatListNavLink: View { } private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert { - Alert( - title: Text("Delete group?"), + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?" + return Alert( + title: Text(label), message: deleteGroupAlertMessage(groupInfo), primaryButton: .destructive(Text("Delete")) { Task { await deleteChat(chat) } @@ -414,8 +512,25 @@ struct ChatListNavLink: View { ) } - private func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { - groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") + private func archiveAllReportsAlert() -> Alert { + Alert( + title: Text("Archive all reports?"), + message: Text("All reports will be archived for you."), + primaryButton: .destructive(Text("Archive")) { + Task { await archiveAllReportsForMe(chat.chatInfo.apiId) } + }, + secondaryButton: .cancel() + ) + } + + private func archiveAllReportsForMe(_ apiId: Int64) async { + do { + if case let .groupChatItemsDeleted(user, groupInfo, chatItemIDs, _, member) = try await apiArchiveReceivedReports(groupId: apiId) { + await groupChatItemsDeleted(user, groupInfo, chatItemIDs, member) + } + } catch { + logger.error("archiveAllReportsForMe error: \(responseError(error))") + } } private func clearChatAlert() -> Alert { @@ -441,9 +556,15 @@ struct ChatListNavLink: View { } private func leaveGroupAlert(_ groupInfo: GroupInfo) -> Alert { - Alert( - title: Text("Leave group?"), - message: Text("You will stop receiving messages from this group. Chat history will be preserved."), + let titleLabel: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group?" : "Leave chat?" + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "You will stop receiving messages from this group. Chat history will be preserved." + : "You will stop receiving messages from this chat. Chat history will be preserved." + ) + return Alert( + title: Text(titleLabel), + message: Text(messageLabel), primaryButton: .destructive(Text("Leave")) { Task { await leaveGroup(groupInfo.groupId) } }, @@ -458,14 +579,14 @@ struct ChatListNavLink: View { ) } - private func invalidJSONPreview(_ json: String) -> some View { + private func invalidJSONPreview(_ json: Data?) -> some View { Text("invalid chat data") .foregroundColor(.red) .padding(4) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .onTapGesture { showInvalidJSON = true } .appSheet(isPresented: $showInvalidJSON) { - invalidJSONView(json) + invalidJSONView(dataToString(json)) .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) } } @@ -474,8 +595,27 @@ struct ChatListNavLink: View { Task { let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { AlertManager.shared.showAlert($0) }) if ok { - ItemsModel.shared.loadOpenChat(contact.id) - AlertManager.shared.showAlert(connReqSentAlert(.contact)) + ItemsModel.shared.loadOpenChat(contact.id) { + AlertManager.shared.showAlert(connReqSentAlert(.contact)) + } + } + } + } +} + +extension View { + @inline(__always) + @ViewBuilder fileprivate func frameCompat(height: CGFloat) -> some View { + if #available(iOS 16, *) { + self.frame(height: height) + } else { + VStack(spacing: 0) { + Divider() + .padding(.leading, 16) + self + .frame(height: height) + .padding(.horizontal, 8) + .padding(.vertical, 8) } } } @@ -567,7 +707,7 @@ func joinGroup(_ groupId: Int64, _ onComplete: @escaping () async -> Void) { } func getErrorAlert(_ error: Error, _ title: LocalizedStringKey) -> ErrorAlert { - if let r = error as? ChatResponse, + if let r = error as? ChatError, let alert = getNetworkErrorAlert(r) { return alert } else { @@ -582,15 +722,15 @@ struct ChatListNavLink_Previews: PreviewProvider { ChatListNavLink(chat: Chat( chatInfo: ChatInfo.sampleData.direct, chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] - )) + ), parentSheet: .constant(nil)) ChatListNavLink(chat: Chat( chatInfo: ChatInfo.sampleData.direct, chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")] - )) + ), parentSheet: .constant(nil)) ChatListNavLink(chat: Chat( chatInfo: ChatInfo.sampleData.contactRequest, chatItems: [] - )) + ), parentSheet: .constant(nil)) } .previewLayout(.fixed(width: 360, height: 82)) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 8ad03236f1..f34f930c6f 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -9,24 +9,154 @@ import SwiftUI import SimpleXChat +enum UserPickerSheet: Identifiable { + case address + case chatPreferences + case chatProfiles + case currentProfile + case useFromDesktop + case settings + + var id: Self { self } + + var navigationTitle: LocalizedStringKey { + switch self { + case .address: "SimpleX address" + case .chatPreferences: "Your preferences" + case .chatProfiles: "Your chat profiles" + case .currentProfile: "Your current profile" + case .useFromDesktop: "Connect to desktop" + case .settings: "Your settings" + } + } +} + +enum PresetTag: Int, Identifiable, CaseIterable, Equatable { + case groupReports = 0 + case favorites = 1 + case contacts = 2 + case groups = 3 + case business = 4 + case notes = 5 + + var id: Int { rawValue } + + var сollapse: Bool { + self != .groupReports + } +} + +enum ActiveFilter: Identifiable, Equatable { + case presetTag(PresetTag) + case userTag(ChatTag) + case unread + + var id: String { + switch self { + case let .presetTag(tag): "preset \(tag.id)" + case let .userTag(tag): "user \(tag.chatTagId)" + case .unread: "unread" + } + } +} + +class SaveableSettings: ObservableObject { + @Published var servers: ServerSettings = ServerSettings(currUserServers: [], userServers: [], serverErrors: []) +} + +struct ServerSettings { + public var currUserServers: [UserOperatorServers] + public var userServers: [UserOperatorServers] + public var serverErrors: [UserServersError] +} + +struct UserPickerSheetView: View { + let sheet: UserPickerSheet + @EnvironmentObject var chatModel: ChatModel + @StateObject private var ss = SaveableSettings() + + @State private var loaded = false + + var body: some View { + NavigationView { + ZStack { + if loaded, let currentUser = chatModel.currentUser { + switch sheet { + case .address: + UserAddressView(shareViaProfile: currentUser.addressShared) + case .chatPreferences: + PreferencesView( + profile: currentUser.profile, + preferences: currentUser.fullPreferences, + currentPreferences: currentUser.fullPreferences + ) + case .chatProfiles: + UserProfilesView() + case .currentProfile: + UserProfile() + case .useFromDesktop: + ConnectDesktopView() + case .settings: + SettingsView() + } + } + Color.clear // Required for list background to be rendered during loading + } + .navigationTitle(sheet.navigationTitle) + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + .overlay { + if let la = chatModel.laRequest { + LocalAuthView(authRequest: la) + } + } + .task { + withAnimation( + .easeOut(duration: 0.1), + { loaded = true } + ) + } + .onDisappear { + if serversCanBeSaved( + ss.servers.currUserServers, + ss.servers.userServers, + ss.servers.serverErrors + ) { + showAlert( + title: NSLocalizedString("Save servers?", comment: "alert title"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: { saveServers($ss.servers.currUserServers, $ss.servers.userServers) }, + cancelButton: true + ) + } + } + .environmentObject(ss) + } +} + struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme - @Binding var showSettings: Bool + @Binding var activeUserPickerSheet: UserPickerSheet? @State private var searchMode = false @FocusState private var searchFocussed @State private var searchText = "" @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil - @State private var userPickerVisible = false - @State private var showConnectDesktop = false @State private var scrollToSearchBar = false + @State private var userPickerShown: Bool = false + @State private var sheet: SomeSheet? = nil + @StateObject private var chatTagsModel = ChatTagsModel.shared + + // iOS 15 is required it to show/hide toolbar while chat is hidden/visible + @State private var viewOnScreen = true - @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @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_ADDRESS_CREATION_CARD_SHOWN) private var addressCreationCardShown = false @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial - + var body: some View { if #available(iOS 16.0, *) { viewBody.scrollDismissesKeyboard(.immediately) @@ -34,7 +164,7 @@ struct ChatListView: View { viewBody } } - + private var viewBody: some View { ZStack(alignment: oneHandUI ? .bottomLeading : .topLeading) { NavStackCompat( @@ -46,24 +176,27 @@ struct ChatListView: View { ), destination: chatView ) { chatListView } - if userPickerVisible { - Rectangle().fill(.white.opacity(0.001)).onTapGesture { - withAnimation { - userPickerVisible.toggle() - } + } + .modifier( + Sheet(isPresented: $userPickerShown) { + UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet) + } + ) + .appSheet( + item: $activeUserPickerSheet, + onDismiss: { chatModel.laRequest = nil }, + content: { UserPickerSheetView(sheet: $0) } + ) + .onChange(of: activeUserPickerSheet) { + if $0 != nil { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + userPickerShown = false } } - UserPicker( - showSettings: $showSettings, - showConnectDesktop: $showConnectDesktop, - userPickerVisible: $userPickerVisible - ) - } - .sheet(isPresented: $showConnectDesktop) { - ConnectDesktopView() } + .environmentObject(chatTagsModel) } - + private var chatListView: some View { let tm = ToolbarMaterial.material(toolbarMaterial) return withToolbar(tm) { @@ -73,7 +206,17 @@ struct ChatListView: View { .navigationBarHidden(searchMode || oneHandUI) } .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .onDisappear() { withAnimation { userPickerVisible = false } } + .onAppear { + if #unavailable(iOS 16.0), !viewOnScreen { + viewOnScreen = true + } + } + .onDisappear { + activeUserPickerSheet = nil + if #unavailable(iOS 16.0) { + viewOnScreen = false + } + } .refreshable { AlertManager.shared.showAlert(Alert( title: Text("Reconnect servers?"), @@ -98,15 +241,22 @@ struct ChatListView: View { Divider().padding(.bottom, Self.hasHomeIndicator ? 0 : 8).background(tm) } } + .sheet(item: $sheet) { sheet in + if #available(iOS 16.0, *) { + sheet.content.presentationDetents([.fraction(sheet.fraction)]) + } else { + sheet.content + } + } } - + static var hasHomeIndicator: Bool = { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first { window.safeAreaInsets.bottom > 0 } else { false } }() - + @ViewBuilder func withToolbar(_ material: Material, content: () -> some View) -> some View { if #available(iOS 16.0, *) { if oneHandUI { @@ -121,19 +271,19 @@ struct ChatListView: View { } } else { if oneHandUI { - content().toolbar { bottomToolbarGroup } + content().toolbar { bottomToolbarGroup() } } else { content().toolbar { topToolbar } } } } - + @ToolbarContentBuilder var topToolbar: some ToolbarContent { ToolbarItem(placement: .topBarLeading) { leadingToolbarItem } ToolbarItem(placement: .principal) { SubsStatusIndicator() } ToolbarItem(placement: .topBarTrailing) { trailingToolbarItem } } - + @ToolbarContentBuilder var bottomToolbar: some ToolbarContent { let padding: Double = Self.hasHomeIndicator ? 0 : 14 ToolbarItem(placement: .bottomBar) { @@ -148,10 +298,10 @@ struct ChatListView: View { .onTapGesture { scrollToSearchBar = true } } } - - @ToolbarContentBuilder var bottomToolbarGroup: some ToolbarContent { + + @ToolbarContentBuilder func bottomToolbarGroup() -> some ToolbarContent { let padding: Double = Self.hasHomeIndicator ? 0 : 14 - ToolbarItemGroup(placement: .bottomBar) { + ToolbarItemGroup(placement: viewOnScreen ? .bottomBar : .principal) { leadingToolbarItem.padding(.bottom, padding) Spacer() SubsStatusIndicator().padding(.bottom, padding) @@ -159,12 +309,12 @@ struct ChatListView: View { trailingToolbarItem.padding(.bottom, padding) } } - + @ViewBuilder var leadingToolbarItem: some View { let user = chatModel.currentUser ?? User.sampleData ZStack(alignment: .topTrailing) { ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel)) - .padding(.trailing, 4) + .padding([.top, .trailing], 3) let allRead = chatModel.users .filter { u in !u.user.activeUser && !u.user.hidden } .allSatisfy { u in u.unreadCount == 0 } @@ -173,16 +323,10 @@ struct ChatListView: View { } } .onTapGesture { - if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 { - withAnimation { - userPickerVisible.toggle() - } - } else { - showSettings = true - } + userPickerShown = true } } - + @ViewBuilder var trailingToolbarItem: some View { switch chatModel.chatRunning { case .some(true): NewChatMenuButton() @@ -190,10 +334,10 @@ struct ChatListView: View { case .none: EmptyView() } } - - @ViewBuilder private var chatList: some View { + + private var chatList: some View { let cs = filteredChats() - ZStack { + return ZStack { ScrollViewReader { scrollProxy in List { if !chatModel.chats.isEmpty { @@ -202,7 +346,8 @@ struct ChatListView: View { searchFocussed: $searchFocussed, searchText: $searchText, searchShowingSimplexLink: $searchShowingSimplexLink, - searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink + searchChatFilteredBySimplexLink: $searchChatFilteredBySimplexLink, + parentSheet: $sheet ) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .listRowSeparator(.hidden) @@ -211,15 +356,9 @@ struct ChatListView: View { .padding(.top, oneHandUI ? 8 : 0) .id("searchBar") } - if !oneHandUICardShown { - OneHandUICard() - .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) + ChatListNavLink(chat: chat, parentSheet: $sheet) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .padding(.trailing, -16) .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) @@ -228,13 +367,7 @@ struct ChatListView: View { .offset(x: -8) } else { ForEach(cs, id: \.viewId) { chat in - VStack(spacing: .zero) { - Divider() - .padding(.leading, 16) - ChatListNavLink(chat: chat) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } + ChatListNavLink(chat: chat, parentSheet: $sheet) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets()) @@ -242,6 +375,20 @@ 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 { + AddressCreationCard() + .padding(.vertical, 6) + .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } } .listStyle(.plain) .onChange(of: chatModel.chatId) { currentChatId in @@ -262,80 +409,97 @@ struct ChatListView: View { } } if cs.isEmpty && !chatModel.chats.isEmpty { - Text("No filtered chats") + noChatsView() .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .foregroundColor(.secondary) } } } + + @ViewBuilder private func noChatsView() -> some View { + if searchString().isEmpty { + switch chatTagsModel.activeFilter { + case .presetTag: Text("No filtered chats") // this should not happen + case let .userTag(tag): Text("No chats in list \(tag.chatTagText)") + case .unread: + Button { + chatTagsModel.activeFilter = nil + } label: { + HStack { + Image(systemName: "line.3.horizontal.decrease") + Text("No unread chats") + } + } + case .none: Text("No chats") + } + } else { + Text("No chats found") + } + } - private func unreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View { + + private func unreadBadge(size: CGFloat = 18) -> some View { Circle() .frame(width: size, height: size) .foregroundColor(theme.colors.primary) } - + @ViewBuilder private func chatView() -> some View { if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) { ChatView(chat: chat) } } - + func stopAudioPlayer() { VoiceItemState.smallView.values.forEach { $0.audioPlayer?.stop() } VoiceItemState.smallView = [:] } - + private func filteredChats() -> [Chat] { if let linkChatId = searchChatFilteredBySimplexLink { return chatModel.chats.filter { $0.id == linkChatId } } else { let s = searchString() - return s == "" && !showUnreadAndFavorites + return s == "" ? chatModel.chats.filter { chat in - !chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card + !chat.chatInfo.chatDeleted && !chat.chatInfo.contactCard && filtered(chat) } : chatModel.chats.filter { chat in let cInfo = chat.chatInfo - switch cInfo { + return switch cInfo { case let .direct(contact): - return !contact.chatDeleted && chatContactType(chat: chat) != ContactType.card && ( - s == "" - ? filtered(chat) - : (viewNameContains(cInfo, s) || - contact.profile.displayName.localizedLowercase.contains(s) || - contact.fullName.localizedLowercase.contains(s)) + !contact.chatDeleted && !chat.chatInfo.contactCard && ( + ( viewNameContains(cInfo, s) || + contact.profile.displayName.localizedLowercase.contains(s) || + contact.fullName.localizedLowercase.contains(s) + ) ) - case let .group(gInfo): - return s == "" - ? (filtered(chat) || gInfo.membership.memberStatus == .memInvited) - : viewNameContains(cInfo, s) - case .local: - return s == "" || viewNameContains(cInfo, s) - case .contactRequest: - return s == "" || viewNameContains(cInfo, s) - case let .contactConnection(conn): - return s != "" && conn.localAlias.localizedLowercase.contains(s) - case .invalidJSON: - return false + case .group: viewNameContains(cInfo, s) + case .local: viewNameContains(cInfo, s) + case .contactRequest: viewNameContains(cInfo, s) + case let .contactConnection(conn): conn.localAlias.localizedLowercase.contains(s) + case .invalidJSON: false } } } - - func searchString() -> String { - searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase - } - + func filtered(_ chat: Chat) -> Bool { - (chat.chatInfo.chatSettings?.favorite ?? false) || - chat.chatStats.unreadChat || - (chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0) + switch chatTagsModel.activeFilter { + case let .presetTag(tag): presetTagMatchesChat(tag, chat.chatInfo, chat.chatStats) + case let .userTag(tag): chat.chatInfo.chatTags?.contains(tag.chatTagId) == true + case .unread: chat.unreadTag + case .none: true + } } - + func viewNameContains(_ cInfo: ChatInfo, _ s: String) -> Bool { cInfo.chatViewName.localizedLowercase.contains(s) } } + + func searchString() -> String { + searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + } } struct SubsStatusIndicator: View { @@ -374,7 +538,7 @@ struct SubsStatusIndicator: View { private func startTask() { task = Task { while !Task.isCancelled { - if AppChatState.shared.value == .active { + if AppChatState.shared.value == .active, ChatModel.shared.chatRunning == true { do { let (subs, hasSess) = try await getAgentSubsTotal() await MainActor.run { @@ -399,18 +563,20 @@ struct SubsStatusIndicator: View { struct ChatListSearchBar: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme + @EnvironmentObject var chatTagsModel: ChatTagsModel @Binding var searchMode: Bool @FocusState.Binding var searchFocussed: Bool @Binding var searchText: String @Binding var searchShowingSimplexLink: Bool @Binding var searchChatFilteredBySimplexLink: String? + @Binding var parentSheet: SomeSheet? @State private var ignoreSearchTextChange = false @State private var alert: PlanAndConnectAlert? @State private var sheet: PlanAndConnectActionSheet? - @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false var body: some View { VStack(spacing: 12) { + ScrollView([.horizontal], showsIndicators: false) { TagsView(parentSheet: $parentSheet, searchText: $searchText) } HStack(spacing: 12) { HStack(spacing: 4) { Image(systemName: "magnifyingglass") @@ -468,6 +634,9 @@ struct ChatListSearchBar: View { } } } + .onChange(of: chatTagsModel.activeFilter) { _ in + searchText = "" + } .alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" }) } @@ -477,16 +646,21 @@ struct ChatListSearchBar: View { } private func toggleFilterButton() -> some View { - ZStack { + let showUnread = chatTagsModel.activeFilter == .unread + return ZStack { Color.clear .frame(width: 22, height: 22) - Image(systemName: showUnreadAndFavorites ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease") + Image(systemName: showUnread ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease") .resizable() .scaledToFit() - .foregroundColor(showUnreadAndFavorites ? theme.colors.primary : theme.colors.secondary) - .frame(width: showUnreadAndFavorites ? 22 : 16, height: showUnreadAndFavorites ? 22 : 16) + .foregroundColor(showUnread ? theme.colors.primary : theme.colors.secondary) + .frame(width: showUnread ? 22 : 16, height: showUnread ? 22 : 16) .onTapGesture { - showUnreadAndFavorites = !showUnreadAndFavorites + if chatTagsModel.activeFilter == .unread { + chatTagsModel.activeFilter = nil + } else { + chatTagsModel.activeFilter = .unread + } } } } @@ -504,6 +678,198 @@ struct ChatListSearchBar: View { } } +struct TagsView: View { + @EnvironmentObject var chatTagsModel: ChatTagsModel + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @Binding var parentSheet: SomeSheet? + @Binding var searchText: String + + var body: some View { + HStack { + tagsView() + } + } + + @ViewBuilder private func tagsView() -> some View { + if chatTagsModel.presetTags.count > 1 { + if chatTagsModel.presetTags.count + chatTagsModel.userTags.count <= 3 { + expandedPresetTagsFiltersView() + } else { + collapsedTagsFilterView() + ForEach(PresetTag.allCases, id: \.id) { (tag: PresetTag) in + if !tag.сollapse && (chatTagsModel.presetTags[tag] ?? 0) > 0 { + expandedTagFilterView(tag) + } + } + } + } + let selectedTag: ChatTag? = if case let .userTag(tag) = chatTagsModel.activeFilter { + tag + } else { + nil + } + ForEach(chatTagsModel.userTags, id: \.id) { tag in + let current = tag == selectedTag + let color: Color = current ? .accentColor : .secondary + ZStack { + HStack(spacing: 4) { + if let emoji = tag.chatTagEmoji { + Text(emoji) + } else { + Image(systemName: current ? "tag.fill" : "tag") + .foregroundColor(color) + } + ZStack { + let badge = Text(verbatim: (chatTagsModel.unreadTags[tag.chatTagId] ?? 0) > 0 ? " ●" : "").font(.footnote) + (Text(tag.chatTagText).fontWeight(.semibold) + badge).foregroundColor(.clear) + Text(tag.chatTagText).fontWeight(current ? .semibold : .regular).foregroundColor(color) + badge.foregroundColor(theme.colors.primary) + } + } + .onTapGesture { + setActiveFilter(filter: .userTag(tag)) + } + .onLongPressGesture { + let screenHeight = UIScreen.main.bounds.height + let reservedSpace: Double = 4 * 44 // 2 for padding, 1 for "Create list" and another for extra tag + let tagsSpace = Double(max(chatTagsModel.userTags.count, 3)) * 44 + let fraction = min((reservedSpace + tagsSpace) / screenHeight, 0.62) + + parentSheet = SomeSheet( + content: { + AnyView( + NavigationView { + TagListView(chat: nil) + .modifier(ThemedBackground(grouped: true)) + } + ) + }, + id: "tag list", + fraction: fraction + ) + } + } + } + + Button { + parentSheet = SomeSheet( + content: { + AnyView( + NavigationView { + TagListEditor() + } + ) + }, + id: "tag create" + ) + } label: { + if chatTagsModel.userTags.isEmpty { + HStack(spacing: 4) { + Image(systemName: "plus") + Text("Add list") + } + } else { + Image(systemName: "plus") + } + } + .foregroundColor(.secondary) + } + + @ViewBuilder private func expandedTagFilterView(_ tag: PresetTag) -> some View { + let selectedPresetTag: PresetTag? = if case let .presetTag(tag) = chatTagsModel.activeFilter { + tag + } else { + nil + } + let active = tag == selectedPresetTag + let (icon, text) = presetTagLabel(tag: tag, active: active) + let color: Color = active ? .accentColor : .secondary + + HStack(spacing: 4) { + Image(systemName: icon) + .foregroundColor(color) + ZStack { + Text(text).fontWeight(.semibold).foregroundColor(.clear) + Text(text).fontWeight(active ? .semibold : .regular).foregroundColor(color) + } + } + .onTapGesture { + setActiveFilter(filter: .presetTag(tag)) + } + } + + private func expandedPresetTagsFiltersView() -> some View { + ForEach(PresetTag.allCases, id: \.id) { tag in + if (chatTagsModel.presetTags[tag] ?? 0) > 0 { + expandedTagFilterView(tag) + } + } + } + + @ViewBuilder private func collapsedTagsFilterView() -> some View { + let selectedPresetTag: PresetTag? = if case let .presetTag(tag) = chatTagsModel.activeFilter { + tag + } else { + nil + } + Menu { + if chatTagsModel.activeFilter != nil || !searchText.isEmpty { + Button { + chatTagsModel.activeFilter = nil + searchText = "" + } label: { + HStack { + Image(systemName: "list.bullet") + Text("All") + } + } + } + ForEach(PresetTag.allCases, id: \.id) { tag in + if (chatTagsModel.presetTags[tag] ?? 0) > 0 && tag.сollapse { + Button { + setActiveFilter(filter: .presetTag(tag)) + } label: { + let (systemName, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag) + HStack { + Image(systemName: systemName) + Text(text) + } + } + } + } + } label: { + if let tag = selectedPresetTag, tag.сollapse { + let (systemName, _) = presetTagLabel(tag: tag, active: true) + Image(systemName: systemName) + .foregroundColor(.accentColor) + } else { + Image(systemName: "list.bullet") + .foregroundColor(.secondary) + } + } + .frame(minWidth: 28) + } + + private func presetTagLabel(tag: PresetTag, active: Bool) -> (String, 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 .business: (active ? "briefcase.fill" : "briefcase", "Businesses") + case .notes: (active ? "folder.fill" : "folder", "Notes") + } + } + + private func setActiveFilter(filter: ActiveFilter) { + if filter != chatTagsModel.activeFilter { + chatTagsModel.activeFilter = filter + } else { + chatTagsModel.activeFilter = nil + } + } +} + func chatStoppedIcon() -> some View { Button { AlertManager.shared.showAlertMsg( @@ -515,7 +881,38 @@ func chatStoppedIcon() -> some View { } } +func presetTagMatchesChat(_ tag: PresetTag, _ chatInfo: ChatInfo, _ chatStats: ChatStats) -> Bool { + switch tag { + case .groupReports: + chatStats.reportsCount > 0 + case .favorites: + chatInfo.chatSettings?.favorite == true + case .contacts: + switch chatInfo { + case let .direct(contact): !(contact.activeConn == nil && contact.profile.contactLink != nil && contact.active) && !contact.chatDeleted + case .contactRequest: true + case .contactConnection: true + case let .group(groupInfo): groupInfo.businessChat?.chatType == .customer + default: false + } + case .groups: + switch chatInfo { + case let .group(groupInfo): groupInfo.businessChat == nil + default: false + } + case .business: + chatInfo.groupInfo?.businessChat?.chatType == .business + case .notes: + switch chatInfo { + case .local: true + default: false + } + } +} + struct ChatListView_Previews: PreviewProvider { + @State static var userPickerSheet: UserPickerSheet? = .none + static var previews: some View { let chatModel = ChatModel() chatModel.updateChats([ @@ -534,9 +931,9 @@ struct ChatListView_Previews: PreviewProvider { ]) return Group { - ChatListView(showSettings: Binding.constant(false)) + ChatListView(activeUserPickerSheet: $userPickerSheet) .environmentObject(chatModel) - ChatListView(showSettings: Binding.constant(false)) + ChatListView(activeUserPickerSheet: $userPickerSheet) .environmentObject(ChatModel()) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 9e6d3005b6..b8c8233e6e 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -27,67 +27,80 @@ struct ChatPreviewView: View { var body: some View { let cItem = chat.chatItems.last - return HStack(spacing: 8) { - ZStack(alignment: .bottomTrailing) { - ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize) - chatPreviewImageOverlayIcon() - .padding([.bottom, .trailing], 1) - } - .padding(.leading, 4) - - VStack(spacing: 0) { - HStack(alignment: .top) { - chatPreviewTitle() - Spacer() - (cItem?.timestampText ?? formatTimestampText(chat.chatInfo.chatTs)) - .font(.subheadline) - .frame(minWidth: 60, alignment: .trailing) - .foregroundColor(theme.colors.secondary) - .padding(.top, 4) + return ZStack { + HStack(spacing: 8) { + ZStack(alignment: .bottomTrailing) { + ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize) + chatPreviewImageOverlayIcon() + .padding([.bottom, .trailing], 1) } - .padding(.bottom, 4) - .padding(.horizontal, 8) - - ZStack(alignment: .topTrailing) { - let chat = activeContentPreview?.chat ?? chat - let ci = activeContentPreview?.ci ?? chat.chatItems.last - let mc = ci?.content.msgContent - HStack(alignment: .top) { - let deleted = ci?.isDeletedContent == true || ci?.meta.itemDeleted != nil - let showContentPreview = (showChatPreviews && chatModel.draftChatId != chat.id && !deleted) || activeContentPreview != nil - if let ci, showContentPreview { - chatItemContentPreview(chat, ci) - } - let mcIsVoice = switch mc { case .voice: true; default: false } - if !mcIsVoice || !showContentPreview || mc?.text != "" || chatModel.draftChatId == chat.id { - let hasFilePreview = if case .file = mc { true } else { false } - chatMessagePreview(cItem, hasFilePreview) - } else { - Spacer() - chatInfoIcon(chat).frame(minWidth: 37, alignment: .trailing) - } - } - .onChange(of: chatModel.stopPreviousRecPlay?.path) { _ in - checkActiveContentPreview(chat, ci, mc) - } - .onChange(of: activeContentPreview) { _ in - checkActiveContentPreview(chat, ci, mc) - } - .onChange(of: showFullscreenGallery) { _ in - checkActiveContentPreview(chat, ci, mc) - } - chatStatusImage() - .padding(.top, dynamicChatInfoSize * 1.44) - .frame(maxWidth: .infinity, alignment: .trailing) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.trailing, 8) + .padding(.leading, 4) - Spacer() + let chatTs = if let cItem { + cItem.meta.itemTs + } else { + chat.chatInfo.chatTs + } + VStack(spacing: 0) { + HStack(alignment: .top) { + chatPreviewTitle() + Spacer() + (formatTimestampText(chatTs)) + .font(.subheadline) + .frame(minWidth: 60, alignment: .trailing) + .foregroundColor(theme.colors.secondary) + .padding(.top, 4) + } + .padding(.bottom, 4) + .padding(.horizontal, 8) + + ZStack(alignment: .topTrailing) { + let chat = activeContentPreview?.chat ?? chat + let ci = activeContentPreview?.ci ?? chat.chatItems.last + let mc = ci?.content.msgContent + HStack(alignment: .top) { + let deleted = ci?.isDeletedContent == true || ci?.meta.itemDeleted != nil + let showContentPreview = (showChatPreviews && chatModel.draftChatId != chat.id && !deleted) || activeContentPreview != nil + if let ci, showContentPreview { + chatItemContentPreview(chat, ci) + } + let mcIsVoice = switch mc { case .voice: true; default: false } + if !mcIsVoice || !showContentPreview || mc?.text != "" || chatModel.draftChatId == chat.id { + let hasFilePreview = if case .file = mc { true } else { false } + chatMessagePreview(cItem, hasFilePreview) + } else { + Spacer() + chatInfoIcon(chat).frame(minWidth: 37, alignment: .trailing) + } + } + .onChange(of: chatModel.stopPreviousRecPlay?.path) { _ in + checkActiveContentPreview(chat, ci, mc) + } + .onChange(of: activeContentPreview) { _ in + checkActiveContentPreview(chat, ci, mc) + } + .onChange(of: showFullscreenGallery) { _ in + checkActiveContentPreview(chat, ci, mc) + } + chatStatusImage() + .padding(.top, dynamicChatInfoSize * 1.44) + .frame(maxWidth: .infinity, alignment: .trailing) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.trailing, 8) + + Spacer() + } + .frame(maxHeight: .infinity) + } + .opacity(deleting ? 0.4 : 1) + .padding(.bottom, -8) + + if deleting { + ProgressView() + .scaleEffect(2) } - .frame(maxHeight: .infinity) } - .padding(.bottom, -8) .onChange(of: chatModel.deletedChats.contains(chat.chatInfo.id)) { contains in deleting = contains // Stop voice when deleting the chat @@ -130,6 +143,7 @@ struct ChatPreviewView: View { } case let .group(groupInfo): switch (groupInfo.membership.memberStatus) { + case .memRejected: inactiveIcon() case .memLeft: inactiveIcon() case .memRemoved: inactiveIcon() case .memGroupDeleted: inactiveIcon() @@ -140,7 +154,7 @@ struct ChatPreviewView: View { } } - @ViewBuilder private func inactiveIcon() -> some View { + private func inactiveIcon() -> some View { Image(systemName: "multiply.circle.fill") .foregroundColor(.secondary.opacity(0.65)) .background(Circle().foregroundColor(Color(uiColor: .systemBackground))) @@ -155,7 +169,7 @@ struct ChatPreviewView: View { let v = previewTitle(t) switch (groupInfo.membership.memberStatus) { case .memInvited: v.foregroundColor(deleting ? theme.colors.secondary : chat.chatInfo.incognito ? .indigo : theme.colors.primary) - case .memAccepted: v.foregroundColor(theme.colors.secondary) + case .memAccepted, .memRejected: v.foregroundColor(theme.colors.secondary) default: if deleting { v.foregroundColor(theme.colors.secondary) } else { v } } default: previewTitle(t) @@ -167,20 +181,23 @@ struct ChatPreviewView: View { } private var verifiedIcon: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .foregroundColor(theme.colors.secondary) .baselineOffset(1) .kerning(-2) } - private func chatPreviewLayout(_ text: Text?, draft: Bool = false, _ hasFilePreview: Bool = false) -> some View { + private func chatPreviewLayout(_ text: Text?, draft: Bool = false, hasFilePreview: Bool = false, hasSecrets: Bool) -> some View { ZStack(alignment: .topTrailing) { + let s = chat.chatStats + let mentionWidth: CGFloat = if s.unreadMentions > 0 && s.unreadCount > 1 { dynamicSize(userFont).unreadCorner } else { 0 } let t = text .lineLimit(userFont <= .xxxLarge ? 2 : 1) .multilineTextAlignment(.leading) + .if(hasSecrets, transform: hiddenSecretsView) .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.leading, hasFilePreview ? 0 : 8) - .padding(.trailing, hasFilePreview ? 38 : 36) + .padding(.trailing, mentionWidth + (hasFilePreview ? 38 : 36)) .offset(x: hasFilePreview ? -2 : 0) .fixedSize(horizontal: false, vertical: true) if !showChatPreviews && !draft { @@ -195,19 +212,34 @@ struct ChatPreviewView: View { @ViewBuilder private func chatInfoIcon(_ chat: Chat) -> some View { let s = chat.chatStats if s.unreadCount > 0 || s.unreadChat { - unreadCountText(s.unreadCount) - .font(userFont <= .xxxLarge ? .caption : .caption2) - .foregroundColor(.white) - .padding(.horizontal, dynamicSize(userFont).unreadPadding) - .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize) - .background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary) - .cornerRadius(dynamicSize(userFont).unreadCorner) - } else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local { - Image(systemName: "speaker.slash.fill") + let mentionColor = mentionColor(chat) + HStack(alignment: .center, spacing: 2) { + if s.unreadMentions > 0 && s.unreadCount > 1 { + Text("\(MENTION_START)") + .font(userFont <= .xxxLarge ? .body : .callout) + .foregroundColor(mentionColor) + .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize) + .cornerRadius(dynamicSize(userFont).unreadCorner) + .padding(.bottom, 1) + } + let singleUnreadIsMention = s.unreadMentions > 0 && s.unreadCount == 1 + (singleUnreadIsMention ? Text("\(MENTION_START)") : unreadCountText(s.unreadCount)) + .font(userFont <= .xxxLarge ? .caption : .caption2) + .foregroundColor(.white) + .padding(.horizontal, dynamicSize(userFont).unreadPadding) + .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize) + .background(singleUnreadIsMention ? mentionColor : chat.chatInfo.ntfsEnabled(false) || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary) + .cornerRadius(dynamicSize(userFont).unreadCorner) + } + .frame(height: dynamicChatInfoSize) + } else if let ntfMode = chat.chatInfo.chatSettings?.enableNtfs, ntfMode != .all { + let iconSize = ntfMode == .mentions ? dynamicChatInfoSize * 0.8 : dynamicChatInfoSize + let iconColor = ntfMode == .mentions ? theme.colors.secondary.opacity(0.7) : theme.colors.secondary + Image(systemName: ntfMode.iconFilled) .resizable() .scaledToFill() - .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize) - .foregroundColor(theme.colors.secondary) + .frame(width: iconSize, height: iconSize) + .foregroundColor(iconColor) } else if chat.chatInfo.chatSettings?.favorite ?? false { Image(systemName: "star.fill") .resizable() @@ -219,20 +251,30 @@ struct ChatPreviewView: View { Color.clear.frame(width: 0) } } + + private func mentionColor(_ chat: Chat) -> Color { + switch chat.chatInfo.chatSettings?.enableNtfs { + case .all: theme.colors.primary + case .mentions: theme.colors.primary + default: theme.colors.secondary + } + } - private func messageDraft(_ draft: ComposeState) -> Text { + private func messageDraft(_ draft: ComposeState) -> (Text, Bool) { let msg = draft.message - return image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) - + attachment() - + messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) + let r = messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(theme.colors.background)) + return (image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) + + attachment() + + Text(AttributedString(r.string)), + r.hasSecrets) func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text { - Text(Image(systemName: s)).foregroundColor(color) + Text(" ") + Text(Image(systemName: s)).foregroundColor(color) + textSpace } func attachment() -> Text { switch draft.preview { - case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + Text(" ") + case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + textSpace case .mediaPreviews: return image("photo") case let .voicePreview(_, duration): return image("play.fill") + Text(durationText(duration)) default: return Text("") @@ -240,19 +282,24 @@ struct ChatPreviewView: View { } } - func chatItemPreview(_ cItem: ChatItem) -> Text { + func chatItemPreview(_ cItem: ChatItem) -> (Text, Bool) { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText() let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) + let r = messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, backgroundColor: UIColor(theme.colors.background), prefix: prefix()) + return (Text(AttributedString(r.string)), r.hasSecrets) // same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey; // can be refactored into a single function if functions calling these are changed to return same type func markedDeletedText() -> String { - switch cItem.meta.itemDeleted { - case let .moderated(_, byGroupMember): String.localizedStringWithFormat(NSLocalizedString("moderated by %@", comment: "marked deleted chat item preview text"), byGroupMember.displayName) - case .blocked: NSLocalizedString("blocked", comment: "marked deleted chat item preview text") - case .blockedByAdmin: NSLocalizedString("blocked by admin", comment: "marked deleted chat item preview text") - case .deleted, nil: NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text") + if cItem.meta.itemDeleted != nil, cItem.isReport { + "archived report" + } else { + switch cItem.meta.itemDeleted { + case let .moderated(_, byGroupMember): String.localizedStringWithFormat(NSLocalizedString("moderated by %@", comment: "marked deleted chat item preview text"), byGroupMember.displayName) + case .blocked: NSLocalizedString("blocked", comment: "marked deleted chat item preview text") + case .blockedByAdmin: NSLocalizedString("blocked by admin", comment: "marked deleted chat item preview text") + case .deleted, nil: NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text") + } } } @@ -265,13 +312,22 @@ struct ChatPreviewView: View { default: return nil } } + + func prefix() -> NSAttributedString? { + switch cItem.content.msgContent { + case let .report(_, reason): reason.attrString + default: nil + } + } } @ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?, _ hasFilePreview: Bool = false) -> some View { if chatModel.draftChatId == chat.id, let draft = chatModel.draft { - chatPreviewLayout(messageDraft(draft), draft: true, hasFilePreview) + let (t, hasSecrets) = messageDraft(draft) + chatPreviewLayout(t, draft: true, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) } else if let cItem = cItem { - chatPreviewLayout(itemStatusMark(cItem) + chatItemPreview(cItem), hasFilePreview) + let (t, hasSecrets) = chatItemPreview(cItem) + chatPreviewLayout(itemStatusMark(cItem) + t, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) } else { switch (chat.chatInfo) { case let .direct(contact): @@ -287,6 +343,7 @@ struct ChatPreviewView: View { } case let .group(groupInfo): switch (groupInfo.membership.memberStatus) { + case .memRejected: chatPreviewInfoText("rejected") case .memInvited: groupInvitationPreviewText(groupInfo) case .memAccepted: chatPreviewInfoText("connecting…") default: EmptyView() @@ -302,7 +359,7 @@ struct ChatPreviewView: View { case let .link(_, preview): smallContentPreview(size: dynamicMediaSize) { ZStack(alignment: .topTrailing) { - Image(uiImage: UIImage(base64Encoded: preview.image) ?? UIImage(systemName: "arrow.up.right")!) + Image(uiImage: imageFromBase64(preview.image) ?? UIImage(systemName: "arrow.up.right")!) .resizable() .aspectRatio(contentMode: .fill) .frame(width: dynamicMediaSize, height: dynamicMediaSize) @@ -318,18 +375,16 @@ struct ChatPreviewView: View { .cornerRadius(8) } .onTapGesture { - UIApplication.shared.open(preview.uri) + openBrowserAlert(uri: preview.uri) } } case let .image(_, image): smallContentPreview(size: dynamicMediaSize) { - CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) - .environmentObject(ReverseListScrollModel()) + CIImageView(chatItem: ci, preview: imageFromBase64(image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) } case let .video(_,image, duration): smallContentPreview(size: dynamicMediaSize) { - CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) - .environmentObject(ReverseListScrollModel()) + CIVideoView(chatItem: ci, preview: imageFromBase64(image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) } case let .voice(_, duration): smallContentPreviewVoice(size: dynamicMediaSize) { @@ -350,7 +405,7 @@ struct ChatPreviewView: View { : chatPreviewInfoText("you are invited to group") } - @ViewBuilder private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { + private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { Text(text) .frame(maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading) .padding([.leading, .trailing], 8) @@ -362,11 +417,11 @@ struct ChatPreviewView: View { case .sndErrorAuth, .sndError: return Text(Image(systemName: "multiply")) .font(.caption) - .foregroundColor(.red) + Text(" ") + .foregroundColor(.red) + textSpace case .sndWarning: return Text(Image(systemName: "exclamationmark.triangle.fill")) .font(.caption) - .foregroundColor(.orange) + Text(" ") + .foregroundColor(.orange) + textSpace default: return Text("") } } @@ -383,6 +438,8 @@ struct ChatPreviewView: View { case .group: if progressByTimeout { ProgressView() + } else if chat.chatStats.reportsCount > 0 { + groupReportsIcon(size: size * 0.8) } else { incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) } @@ -428,6 +485,14 @@ struct ChatPreviewView: View { } } +func groupReportsIcon(size: CGFloat) -> some View { + Image(systemName: "flag") + .resizable() + .scaledToFit() + .frame(width: size, height: size) + .foregroundColor(.red) +} + func smallContentPreview(size: CGFloat, _ view: @escaping () -> some View) -> some View { view() .frame(width: size, height: size) diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 0f64b632dc..b9f5b984e1 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -14,6 +14,7 @@ struct ContactConnectionInfo: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction @State var contactConnection: PendingContactConnection + @State private var showShortLink: Bool = true @State private var alert: CCInfoAlert? @State private var localAlias = "" @State private var showIncognitoSheet = false @@ -61,14 +62,19 @@ struct ContactConnectionInfo: View { } if contactConnection.initiated, - let connReqInv = contactConnection.connReqInv { - SimpleXLinkQRCode(uri: simplexChatLink(connReqInv)) + let connLinkInv = contactConnection.connLinkInv { + SimpleXCreatedLinkQRCode(link: connLinkInv, short: $showShortLink) + .id("simplex-invitation-qrcode-\(connLinkInv.simplexChatUri(short: showShortLink))") incognitoEnabled() - shareLinkButton(connReqInv, theme.colors.secondary) - oneTimeLinkLearnMoreButton(theme.colors.secondary) + shareLinkButton(connLinkInv, short: showShortLink) + oneTimeLinkLearnMoreButton() } else { incognitoEnabled() - oneTimeLinkLearnMoreButton(theme.colors.secondary) + oneTimeLinkLearnMoreButton() + } + } header: { + if let connLinkInv = contactConnection.connLinkInv, connLinkInv.connShortLink != nil { + ToggleShortLinkHeader(text: Text(""), link: connLinkInv, short: $showShortLink) } } footer: { sharedProfileInfo(contactConnection.incognito) @@ -167,26 +173,22 @@ struct ContactConnectionInfo: View { } } -private func shareLinkButton(_ connReqInvitation: String, _ secondaryColor: Color) -> some View { +private func shareLinkButton(_ connLinkInvitation: CreatedConnLink, short: Bool) -> some View { Button { - showShareSheet(items: [simplexChatLink(connReqInvitation)]) + showShareSheet(items: [connLinkInvitation.simplexChatUri(short: short)]) } label: { - settingsRow("square.and.arrow.up", color: secondaryColor) { - Text("Share 1-time link") - } + Label("Share 1-time link", systemImage: "square.and.arrow.up") } } -private func oneTimeLinkLearnMoreButton(_ secondaryColor: Color) -> some View { +private func oneTimeLinkLearnMoreButton() -> some View { NavigationLink { AddContactLearnMore(showTitle: false) .navigationTitle("One-time invitation link") .modifier(ThemedBackground()) .navigationBarTitleDisplayMode(.large) } label: { - settingsRow("info.circle", color: secondaryColor) { - Text("Learn more") - } + Label("Learn more", systemImage: "info.circle") } } diff --git a/apps/ios/Shared/Views/ChatList/OneHandUICard.swift b/apps/ios/Shared/Views/ChatList/OneHandUICard.swift index 636d165114..059f24cc82 100644 --- a/apps/ios/Shared/Views/ChatList/OneHandUICard.swift +++ b/apps/ios/Shared/Views/ChatList/OneHandUICard.swift @@ -32,7 +32,6 @@ struct OneHandUICard: View { .background(theme.appColors.sentMessage) .cornerRadius(12) .frame(height: dynamicSize(userFont).rowHeight) - .padding(.vertical, 12) .alert(isPresented: $showOneHandUIAlert) { Alert( title: Text("Reachable chat toolbar"), diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 477a78e36d..8b0a8af888 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -245,7 +245,7 @@ struct ServersSummaryView: View { } } - @ViewBuilder private func smpServersListView( + private func smpServersListView( _ servers: [SMPServerSummary], _ statsStartedAt: Date, _ header: LocalizedStringKey? = nil, @@ -256,7 +256,7 @@ struct ServersSummaryView: View { ? serverAddress($0.smpServer) < serverAddress($1.smpServer) : $0.hasSubs && !$1.hasSubs } - Section { + return Section { ForEach(sortedServers) { server in smpServerView(server, statsStartedAt) } @@ -318,14 +318,14 @@ struct ServersSummaryView: View { return onionHosts == .require ? .indigo : .accentColor } - @ViewBuilder private func xftpServersListView( + private func xftpServersListView( _ servers: [XFTPServerSummary], _ statsStartedAt: Date, _ header: LocalizedStringKey? = nil, _ footer: LocalizedStringKey? = nil ) -> some View { let sortedServers = servers.sorted { serverAddress($0.xftpServer) < serverAddress($1.xftpServer) } - Section { + return Section { ForEach(sortedServers) { server in xftpServerView(server, statsStartedAt) } @@ -407,12 +407,18 @@ struct ServersSummaryView: View { struct SubscriptionStatusIndicatorView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) if #available(iOS 16.0, *) { Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) .foregroundColor(color) @@ -425,26 +431,32 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) Text(verbatim: "\(Int(floor(statusPercent * 100)))%") .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) { +func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 : (n * 4).rounded() / 4 } - let activeColor: Color = onionHosts == .require ? .indigo : .accentColor + let activeColor: Color = usesProxy ? .indigo : primaryColor let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0) let activeSubsRounded = roundedToQuarter(subs.shareOfActive) @@ -479,15 +491,6 @@ struct SMPServerSummaryView: View { Section("Server address") { Text(summary.smpServer) .textSelection(.enabled) - if summary.known == true { - NavigationLink { - ProtocolServersView(serverProtocol: .smp) - .navigationTitle("Your SMP servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Open server settings") - } - } } if let stats = summary.stats { @@ -584,7 +587,7 @@ struct SMPStatsView: View { } header: { Text("Statistics") } footer: { - Text("Starting from \(localTimestamp(statsStartedAt)).") + Text("\n") + Text("All data is private to your device.") + Text("Starting from \(localTimestamp(statsStartedAt)).") + textNewLine + Text("All data is kept private on your device.") } } } @@ -667,15 +670,6 @@ struct XFTPServerSummaryView: View { Section("Server address") { Text(summary.xftpServer) .textSelection(.enabled) - if summary.known == true { - NavigationLink { - ProtocolServersView(serverProtocol: .xftp) - .navigationTitle("Your XFTP servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Open server settings") - } - } } if let stats = summary.stats { @@ -709,7 +703,7 @@ struct XFTPStatsView: View { } header: { Text("Statistics") } footer: { - Text("Starting from \(localTimestamp(statsStartedAt)).") + Text("\n") + Text("All data is private to your device.") + Text("Starting from \(localTimestamp(statsStartedAt)).") + textNewLine + Text("All data is kept private on your device.") } } } diff --git a/apps/ios/Shared/Views/ChatList/TagListView.swift b/apps/ios/Shared/Views/ChatList/TagListView.swift new file mode 100644 index 0000000000..2063fe15de --- /dev/null +++ b/apps/ios/Shared/Views/ChatList/TagListView.swift @@ -0,0 +1,408 @@ +// +// TagListView.swift +// SimpleX (iOS) +// +// Created by Diogo Cunha on 31/12/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat +import ElegantEmojiPicker + +struct TagEditorNavParams { + let chat: Chat? + let chatListTag: ChatTagData? + let tagId: Int64? +} + +struct TagListView: View { + var chat: Chat? = nil + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + @EnvironmentObject var chatTagsModel: ChatTagsModel + @EnvironmentObject var m: ChatModel + @State private var editMode = EditMode.inactive + @State private var tagEditorNavParams: TagEditorNavParams? = nil + + var chatTagsIds: [Int64] { chat?.chatInfo.contact?.chatTags ?? chat?.chatInfo.groupInfo?.chatTags ?? [] } + + var body: some View { + List { + Section { + ForEach(chatTagsModel.userTags, id: \.id) { tag in + let text = tag.chatTagText + let emoji = tag.chatTagEmoji + let tagId = tag.chatTagId + let selected = chatTagsIds.contains(tagId) + + HStack { + if let emoji { + Text(emoji) + } else { + Image(systemName: "tag") + } + Text(text) + .padding(.leading, 12) + Spacer() + if chat != nil { + radioButton(selected: selected) + } + } + .contentShape(Rectangle()) + .onTapGesture { + if let c = chat { + setChatTag(tagId: selected ? nil : tagId, chat: c) { dismiss() } + } else { + tagEditorNavParams = TagEditorNavParams(chat: nil, chatListTag: ChatTagData(emoji: emoji, text: text), tagId: tagId) + } + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button { + showAlert( + NSLocalizedString("Delete list?", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("All chats will be removed from the list %@, and the list deleted.", comment: "alert message"), text), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default + ), + UIAlertAction( + title: NSLocalizedString("Delete", comment: "alert action"), + style: .destructive, + handler: { _ in + deleteTag(tagId) + } + ) + ]} + ) + } label: { + Label("Delete", systemImage: "trash.fill") + } + .tint(.red) + } + .swipeActions(edge: .leading, allowsFullSwipe: true) { + Button { + tagEditorNavParams = TagEditorNavParams(chat: nil, chatListTag: ChatTagData(emoji: emoji, text: text), tagId: tagId) + } label: { + Label("Edit", systemImage: "pencil") + } + .tint(theme.colors.primary) + } + .background( + // isActive required to navigate to edit view from any possible tag edited in swipe action + NavigationLink(isActive: Binding(get: { tagEditorNavParams != nil }, set: { _ in tagEditorNavParams = nil })) { + if let params = tagEditorNavParams { + TagListEditor( + chat: params.chat, + tagId: params.tagId, + emoji: params.chatListTag?.emoji, + name: params.chatListTag?.text ?? "" + ) + } + } label: { + EmptyView() + } + .opacity(0) + ) + } + .onMove(perform: moveItem) + + NavigationLink { + TagListEditor(chat: chat) + } label: { + Label("Create list", systemImage: "plus") + } + } header: { + if chat == nil { + editTagsButton() + .textCase(nil) + .frame(maxWidth: .infinity, alignment: .trailing) + } + } + } + .modifier(ThemedBackground(grouped: true)) + .environment(\.editMode, $editMode) + } + + private func editTagsButton() -> some View { + if editMode.isEditing { + Button("Done") { + editMode = .inactive + dismiss() + } + } else { + Button("Edit") { + editMode = .active + } + } + } + + private func radioButton(selected: Bool) -> some View { + Image(systemName: selected ? "checkmark.circle.fill" : "circle") + .imageScale(.large) + .foregroundStyle(selected ? Color.accentColor : Color(.tertiaryLabel)) + } + + private func moveItem(from source: IndexSet, to destination: Int) { + Task { + do { + var tags = chatTagsModel.userTags + tags.move(fromOffsets: source, toOffset: destination) + try await apiReorderChatTags(tagIds: tags.map { $0.chatTagId }) + + await MainActor.run { + chatTagsModel.userTags = tags + } + } catch let error { + showAlert( + NSLocalizedString("Error reordering lists", comment: "alert title"), + message: responseError(error) + ) + } + } + } + + private func deleteTag(_ tagId: Int64) { + Task { + try await apiDeleteChatTag(tagId: tagId) + + await MainActor.run { + chatTagsModel.userTags = chatTagsModel.userTags.filter { $0.chatTagId != tagId } + if case let .userTag(tag) = chatTagsModel.activeFilter, tagId == tag.chatTagId { + chatTagsModel.activeFilter = nil + } + m.chats.forEach { c in + if var contact = c.chatInfo.contact, contact.chatTags.contains(tagId) { + contact.chatTags = contact.chatTags.filter({ $0 != tagId }) + m.updateContact(contact) + } else if var group = c.chatInfo.groupInfo, group.chatTags.contains(tagId) { + group.chatTags = group.chatTags.filter({ $0 != tagId }) + m.updateGroup(group) + } + } + } + } + } +} + +private func setChatTag(tagId: Int64?, chat: Chat, closeSheet: @escaping () -> Void) { + Task { + do { + let tagIds: [Int64] = if let t = tagId { [t] } else {[]} + let (userTags, chatTags) = try await apiSetChatTags( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + tagIds: tagIds + ) + + await MainActor.run { + let m = ChatModel.shared + let tm = ChatTagsModel.shared + tm.userTags = userTags + if chat.unreadTag, let tags = chat.chatInfo.chatTags { + tm.decTagsReadCount(tags) + } + if var contact = chat.chatInfo.contact { + contact.chatTags = chatTags + m.updateContact(contact) + } else if var group = chat.chatInfo.groupInfo { + group.chatTags = chatTags + m.updateGroup(group) + } + ChatTagsModel.shared.updateChatTagRead(chat, wasUnread: false) + closeSheet() + } + } catch let error { + showAlert( + NSLocalizedString("Error saving chat list", comment: "alert title"), + message: responseError(error) + ) + } + } +} + +struct EmojiPickerView: UIViewControllerRepresentable { + @Binding var selectedEmoji: String? + @Binding var showingPicker: Bool + @Environment(\.presentationMode) var presentationMode + + class Coordinator: NSObject, ElegantEmojiPickerDelegate, UIAdaptivePresentationControllerDelegate { + var parent: EmojiPickerView + + init(parent: EmojiPickerView) { + self.parent = parent + } + + func emojiPicker(_ picker: ElegantEmojiPicker, didSelectEmoji emoji: Emoji?) { + parent.selectedEmoji = emoji?.emoji + parent.showingPicker = false + picker.dismiss(animated: true) + } + + // Called when the picker is dismissed manually (without selection) + func presentationControllerWillDismiss(_ presentationController: UIPresentationController) { + parent.showingPicker = false + } + } + + func makeCoordinator() -> Coordinator { + return Coordinator(parent: self) + } + + func makeUIViewController(context: Context) -> UIViewController { + let config = ElegantConfiguration(showRandom: false, showReset: true, showClose: false) + let picker = ElegantEmojiPicker(delegate: context.coordinator, configuration: config) + + picker.presentationController?.delegate = context.coordinator + + let viewController = UIViewController() + DispatchQueue.main.async { + if let topVC = getTopViewController() { + topVC.present(picker, animated: true) + } + } + + return viewController + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + // No need to update the controller after creation + } +} + +struct TagListEditor: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var chatTagsModel: ChatTagsModel + @EnvironmentObject var theme: AppTheme + var chat: Chat? = nil + var tagId: Int64? = nil + var emoji: String? + var name: String = "" + @State private var newEmoji: String? + @State private var newName: String = "" + @State private var isPickerPresented = false + @State private var saving: Bool? + + var body: some View { + VStack { + List { + let isDuplicateEmojiOrName = chatTagsModel.userTags.contains { tag in + tag.chatTagId != tagId && + ((newEmoji != nil && tag.chatTagEmoji == newEmoji) || tag.chatTagText == trimmedName) + } + + Section { + HStack { + Button { + isPickerPresented = true + } label: { + if let newEmoji { + Text(newEmoji) + } else { + Image(systemName: "face.smiling") + .foregroundColor(.secondary) + } + } + TextField("List name...", text: $newName) + } + + Button { + saving = true + if let tId = tagId { + updateChatTag(tagId: tId, chatTagData: ChatTagData(emoji: newEmoji, text: trimmedName)) + } else { + createChatTag() + } + } label: { + Text( + chat != nil + ? "Add to list" + : "Save list" + ) + } + .disabled(saving != nil || (trimmedName == name && newEmoji == emoji) || trimmedName.isEmpty || isDuplicateEmojiOrName) + } footer: { + if isDuplicateEmojiOrName && saving != false { // if not saved already, to prevent flickering + HStack { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + Text("List name and emoji should be different for all lists.") + .foregroundColor(theme.colors.secondary) + } + } + } + } + + if isPickerPresented { + EmojiPickerView(selectedEmoji: $newEmoji, showingPicker: $isPickerPresented) + } + } + .modifier(ThemedBackground(grouped: true)) + .onAppear { + newEmoji = emoji + newName = name + } + } + + var trimmedName: String { + newName.trimmingCharacters(in: .whitespaces) + } + + private func createChatTag() { + Task { + do { + let text = trimmedName + let userTags = try await apiCreateChatTag( + tag: ChatTagData(emoji: newEmoji , text: text) + ) + await MainActor.run { + saving = false + chatTagsModel.userTags = userTags + } + if let chat, let tag = userTags.first(where: { $0.chatTagText == text && $0.chatTagEmoji == newEmoji}) { + setChatTag(tagId: tag.chatTagId, chat: chat) { dismiss() } + } else { + await MainActor.run { dismiss() } + } + } catch let error { + await MainActor.run { + saving = nil + showAlert( + NSLocalizedString("Error creating list", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } + + private func updateChatTag(tagId: Int64, chatTagData: ChatTagData) { + Task { + do { + try await apiUpdateChatTag(tagId: tagId, tag: chatTagData) + await MainActor.run { + saving = false + for i in 0.. 375 ? 20 : 16 } + private let sectionShape = RoundedRectangle(cornerRadius: 10, style: .continuous) var body: some View { - VStack { - Spacer().frame(height: 1) + let otherUsers: [UserInfo] = m.users + .filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } + .sorted(using: KeyPathComparator(\.user.activeOrder, order: .reverse)) + let sectionWidth = max(frameWidth - sectionHorizontalPadding * 2, 0) + let currentUserWidth = max(frameWidth - sectionHorizontalPadding - rowPadding * 2 - 14 - imageSize, 0) + let stopped = m.chatRunning != true + VStack(spacing: sectionSpacing) { + if let user = m.currentUser { + StickyScrollView(resetScroll: $resetScroll) { + HStack(spacing: rowPadding) { + HStack { + ProfileImage(imageStr: user.image, size: imageSize, color: Color(uiColor: .tertiarySystemGroupedBackground)) + .padding(.trailing, 6) + profileName(user).lineLimit(1) + } + .padding(rowPadding) + .frame(width: otherUsers.isEmpty ? sectionWidth : currentUserWidth, alignment: .leading) + .modifier(ListRow { activeSheet = .currentProfile }) + .clipShape(sectionShape) + .disabled(stopped) + .opacity(stopped ? 0.4 : 1) + ForEach(otherUsers) { u in + userView(u, size: imageSize) + .frame(maxWidth: sectionWidth * 0.618) + .fixedSize() + .disabled(stopped) + .opacity(stopped ? 0.4 : 1) + } + } + .padding(.horizontal, sectionHorizontalPadding) + } + .frame(height: 2 * rowPadding + imageSize) + .padding(.top, sectionSpacing) + .overlay(DetermineWidth()) + .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } + } VStack(spacing: 0) { - ScrollView { - ScrollViewReader { sp in - let users = m.users - .filter({ u in u.user.activeUser || !u.user.hidden }) - .sorted { u, _ in u.user.activeUser } - VStack(spacing: 0) { - ForEach(users) { u in - userView(u) - Divider() - if u.user.activeUser { Divider() } + openSheetOnTap("qrcode", title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", sheet: .address, disabled: stopped) + openSheetOnTap("switch.2", title: "Chat preferences", sheet: .chatPreferences, disabled: stopped) + openSheetOnTap("person.crop.rectangle.stack", title: "Your chat profiles", sheet: .chatProfiles, disabled: stopped) + openSheetOnTap("desktopcomputer", title: "Use from desktop", sheet: .useFromDesktop, disabled: stopped) + ZStack(alignment: .trailing) { + openSheetOnTap("gearshape", title: "Settings", sheet: .settings, showDivider: false) + Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") + .resizable() + .scaledToFit() + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(maxWidth: 20, maxHeight: .infinity) + .padding(.horizontal, rowPadding) + .background(Color(.systemBackground).opacity(0.01)) + .onTapGesture { + if (colorScheme == .light) { + ThemeManager.applyTheme(systemDarkThemeDefault.get()) + } else { + ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) } } - .overlay { - GeometryReader { geo -> Color in - DispatchQueue.main.async { - scrollViewContentSize = geo.size - let scenes = UIApplication.shared.connectedScenes - if let windowScene = scenes.first as? UIWindowScene { - let layoutFrame = windowScene.windows[0].safeAreaLayoutGuide.layoutFrame - disableScrolling = scrollViewContentSize.height + menuButtonHeight + 10 < layoutFrame.height - } - } - return Color.clear - } + .onLongPressGesture { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } - .onChange(of: userPickerVisible) { visible in - if visible, let u = users.first { - sp.scrollTo(u.id) - } - } - } - } - .simultaneousGesture(DragGesture(minimumDistance: disableScrolling ? 0 : 10000000)) - .frame(maxHeight: scrollViewContentSize.height) - - menuButton("Use from desktop", icon: "desktopcomputer") { - showConnectDesktop = true - withAnimation { - userPickerVisible.toggle() - } - } - Divider() - menuButton("Settings", icon: "gearshape") { - showSettings = true - withAnimation { - userPickerVisible.toggle() - } } } + .clipShape(sectionShape) + .padding(.horizontal, sectionHorizontalPadding) + .padding(.bottom, sectionSpacing) } - .clipShape(RoundedRectangle(cornerRadius: 16)) - .background( - Rectangle() - .fill(theme.colors.surface) - .cornerRadius(16) - .shadow(color: .black.opacity(0.12), radius: 24, x: 0, y: 0) - ) - .onPreferenceChange(DetermineWidth.Key.self) { chatViewNameWidth = $0 } - .frame(maxWidth: chatViewNameWidth > 0 ? min(300, chatViewNameWidth + 130) : 300) - .padding(8) - .opacity(userPickerVisible ? 1.0 : 0.0) .onAppear { - // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { - Task { - do { - let users = try await listUsersAsync() - await MainActor.run { m.users = users } - } catch { - logger.error("Error loading users \(responseError(error))") - } - } - } - } - } - - private func userView(_ u: UserInfo) -> some View { - let user = u.user - return Button(action: { - if user.activeUser { - showSettings = true - withAnimation { - userPickerVisible.toggle() - } - } else { + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { + currentUser = m.currentUser?.userId Task { do { - try await changeActiveUserAsync_(user.userId, viewPwd: nil) - await MainActor.run { userPickerVisible = false } - } catch { + let users = try await listUsersAsync() await MainActor.run { - AlertManager.shared.showAlertMsg( - title: "Error switching profile!", - message: "Error: \(responseError(error))" - ) + m.users = users + currentUser = m.currentUser?.userId } + } catch { + logger.error("Error loading users \(responseError(error))") } } } - }, label: { - HStack(spacing: 0) { - ProfileImage(imageStr: user.image, size: 44, color: Color(uiColor: .tertiarySystemFill)) - .padding(.trailing, 12) - Text(user.chatViewName) - .fontWeight(user.activeUser ? .medium : .regular) - .foregroundColor(theme.colors.onBackground) - .overlay(DetermineWidth()) - Spacer() - if user.activeUser { - Image(systemName: "checkmark") - } else if u.unreadCount > 0 { - unreadCounter(u.unreadCount, color: user.showNtfs ? theme.colors.primary : theme.colors.secondary) - } else if !user.showNtfs { - Image(systemName: "speaker.slash") + } + .onChange(of: userPickerShown) { + if !$0 { resetScroll() } + } + .modifier(ThemedBackground(grouped: true)) + .disabled(switchingProfile) + } + + private func userView(_ u: UserInfo, size: CGFloat) -> some View { + HStack { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) + if (u.unreadCount > 0) { + UnreadBadge(userInfo: u).offset(x: 4, y: -4) } } - .padding(.trailing) - .padding([.leading, .vertical], 12) - }) - .buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill))) - } - - private func menuButton(_ title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { - Button(action: action) { - HStack(spacing: 0) { - Text(title) - .overlay(DetermineWidth()) - Spacer() - Image(systemName: icon) - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - } - .padding(.horizontal) - .padding(.vertical, 22) - .frame(height: menuButtonHeight) + .padding(.trailing, 6) + Text(u.user.displayName).font(.title2).lineLimit(1) + } + .padding(rowPadding) + .modifier(ListRow { + switchingProfile = true + Task { + do { + try await changeActiveUserAsync_(u.user.userId, viewPwd: nil) + await MainActor.run { + switchingProfile = false + userPickerShown = false + } + } catch { + await MainActor.run { + switchingProfile = false + showAlert( + NSLocalizedString("Error switching profile!", comment: "alertTitle"), + message: String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "alert message"), responseError(error)) + ) + } + } + } + }) + .clipShape(sectionShape) + } + + private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet, showDivider: Bool = true, disabled: Bool = false) -> some View { + ZStack(alignment: .bottom) { + settingsRow(icon, color: theme.colors.secondary) { + Text(title).foregroundColor(.primary).opacity(disabled ? 0.4 : 1) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, rowPadding) + .padding(.vertical, rowVerticalPadding) + .modifier(ListRow { activeSheet = sheet }) + .disabled(disabled) + if showDivider { + Divider().padding(.leading, 52) + } } - .buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill))) } } -private func unreadCounter(_ unread: Int, color: Color) -> some View { - unreadCountText(unread) - .font(.caption) - .foregroundColor(.white) - .padding(.horizontal, 4) - .frame(minWidth: 18, minHeight: 18) - .background(color) - .cornerRadius(10) +struct UnreadBadge: View { + var userInfo: UserInfo + @EnvironmentObject var theme: AppTheme + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + + var body: some View { + let size = dynamicSize(userFont).chatInfoSize + unreadCountText(userInfo.unreadCount) + .font(userFont <= .xxxLarge ? .caption : .caption2) + .foregroundColor(.white) + .padding(.horizontal, dynamicSize(userFont).unreadPadding) + .frame(minWidth: size, minHeight: size) + .background(userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary) + .cornerRadius(dynamicSize(userFont).unreadCorner) + } } +struct ListRow: ViewModifier { + @Environment(\.colorScheme) private var colorScheme: ColorScheme + @State private var touchDown = false + let action: () -> Void + + func body(content: Content) -> some View { + ZStack { + elevatedSecondarySystemGroupedBackground + Color(.systemGray4).opacity(touchDown ? 1 : 0) + content + TouchOverlay(touchDown: $touchDown, action: action) + } + } + + var elevatedSecondarySystemGroupedBackground: Color { + switch colorScheme { + case .dark: Color(0xFF2C2C2E) + default: Color(0xFFFFFFFF) + } + } + + struct TouchOverlay: UIViewRepresentable { + @Binding var touchDown: Bool + let action: () -> Void + + func makeUIView(context: Context) -> TouchView { + let touchView = TouchView() + let gesture = UILongPressGestureRecognizer( + target: touchView, + action: #selector(touchView.longPress(gesture:)) + ) + gesture.delegate = touchView + gesture.minimumPressDuration = 0 + touchView.addGestureRecognizer(gesture) + return touchView + } + + func updateUIView(_ touchView: TouchView, context: Context) { + touchView.representer = self + } + + class TouchView: UIView, UIGestureRecognizerDelegate { + var representer: TouchOverlay? + private var startLocation: CGPoint? + private var task: Task? + + @objc + func longPress(gesture: UILongPressGestureRecognizer) { + switch gesture.state { + case .began: + startLocation = gesture.location(in: nil) + task = Task { + do { + try await Task.sleep(nanoseconds: 200_000000) + await MainActor.run { representer?.touchDown = true } + } catch { } + } + case .ended: + if hitTest(gesture.location(in: self), with: nil) == self { + representer?.action() + } + task?.cancel() + representer?.touchDown = false + case .changed: + if let startLocation { + let location = gesture.location(in: nil) + let dx = location.x - startLocation.x + let dy = location.y - startLocation.y + if sqrt(pow(dx, 2) + pow(dy, 2)) > 10 { gesture.state = .failed } + } + case .cancelled, .failed: + task?.cancel() + representer?.touchDown = false + default: break + } + } + + func gestureRecognizer( + _: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith: UIGestureRecognizer + ) -> Bool { true } + } + } +} + + struct UserPicker_Previews: PreviewProvider { static var previews: some View { + @State var activeSheet: UserPickerSheet? + let m = ChatModel() m.users = [UserInfo.sampleData, UserInfo.sampleData] return UserPicker( - showSettings: Binding.constant(false), - showConnectDesktop: Binding.constant(false), - userPickerVisible: Binding.constant(true) + userPickerShown: .constant(true), + activeSheet: $activeSheet ) .environmentObject(m) } diff --git a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift index 4b43610236..456c46d318 100644 --- a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift +++ b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift @@ -20,7 +20,7 @@ struct ContactListNavLink: View { @State private var showContactRequestDialog = false var body: some View { - let contactType = chatContactType(chat: chat) + let contactType = chatContactType(chat) Group { switch (chat.chatInfo) { @@ -140,9 +140,9 @@ struct ContactListNavLink: View { } } - @ViewBuilder private func previewTitle(_ contact: Contact, titleColor: Color) -> some View { + private func previewTitle(_ contact: Contact, titleColor: Color) -> some View { let t = Text(chat.chatInfo.chatViewName).foregroundColor(titleColor) - ( + return ( contact.verified == true ? verifiedIcon + t : t @@ -151,7 +151,7 @@ struct ContactListNavLink: View { } private var verifiedIcon: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .foregroundColor(.secondary) .baselineOffset(1) .kerning(-2) @@ -188,8 +188,7 @@ struct ContactListNavLink: View { Task { let ok = await connectContactViaAddress(contact.contactId, incognito, showAlert: { alert = SomeAlert(alert: $0, id: "ContactListNavLink connectContactViaAddress") }) if ok { - ItemsModel.shared.loadOpenChat(contact.id) - DispatchQueue.main.async { + ItemsModel.shared.loadOpenChat(contact.id) { dismissAllSheets(animated: true) { AlertManager.shared.showAlert(connReqSentAlert(.contact)) } diff --git a/apps/ios/Shared/Views/Database/ChatArchiveView.swift b/apps/ios/Shared/Views/Database/ChatArchiveView.swift deleted file mode 100644 index 3ab4ac9a31..0000000000 --- a/apps/ios/Shared/Views/Database/ChatArchiveView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// ChatArchiveView.swift -// SimpleXChat -// -// Created by Evgeny on 23/06/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct ChatArchiveView: View { - @EnvironmentObject var theme: AppTheme - var archiveName: String - @AppStorage(DEFAULT_CHAT_ARCHIVE_NAME) private var chatArchiveName: String? - @AppStorage(DEFAULT_CHAT_ARCHIVE_TIME) private var chatArchiveTime: Double = 0 - @State private var showDeleteAlert = false - - var body: some View { - let fileUrl = getDocumentsDirectory().appendingPathComponent(archiveName) - let fileTs = chatArchiveTimeDefault.get() - List { - Section { - settingsRow("square.and.arrow.up", color: theme.colors.secondary) { - Button { - showShareSheet(items: [fileUrl]) - } label: { - Text("Save archive") - } - } - settingsRow("trash", color: theme.colors.secondary) { - Button { - showDeleteAlert = true - } label: { - Text("Delete archive").foregroundColor(.red) - } - } - } header: { - Text("Chat archive") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("Created on \(fileTs)") - .foregroundColor(theme.colors.secondary) - } - } - .alert(isPresented: $showDeleteAlert) { - Alert( - title: Text("Delete chat archive?"), - primaryButton: .destructive(Text("Delete")) { - do { - try FileManager.default.removeItem(atPath: fileUrl.path) - chatArchiveName = nil - chatArchiveTime = 0 - } catch let error { - logger.error("removeItem error \(String(describing: error))") - } - }, - secondaryButton: .cancel() - ) - } - } -} - -struct ChatArchiveView_Previews: PreviewProvider { - static var previews: some View { - ChatArchiveView(archiveName: "") - } -} diff --git a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift index be167b92b9..441a164f8a 100644 --- a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift @@ -48,6 +48,8 @@ struct DatabaseEncryptionView: View { @State private var confirmNewKey = "" @State private var currentKeyShown = false + let stopChatRunBlockStartChat: (Binding, @escaping () async throws -> Bool) -> Void + var body: some View { ZStack { List { @@ -134,46 +136,61 @@ struct DatabaseEncryptionView: View { .onAppear { if initialRandomDBPassphrase { currentKey = kcDatabasePassword.get() ?? "" } } - .disabled(m.chatRunning != false) + .disabled(progressIndicator) .alert(item: $alert) { item in databaseEncryptionAlert(item) } } - private func encryptDatabase() { - progressIndicator = true - Task { - do { - encryptionStartedDefault.set(true) - encryptionStartedAtDefault.set(Date.now) - if !m.chatDbChanged { - try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) - } - try await apiStorageEncryption(currentKey: currentKey, newKey: newKey) - encryptionStartedDefault.set(false) - initialRandomDBPassphraseGroupDefault.set(false) - if migration { - storeDBPassphraseGroupDefault.set(useKeychain) - } - if useKeychain { - if kcDatabasePassword.set(newKey) { - await resetFormAfterEncryption(true) - await operationEnded(.databaseEncrypted) - } else { - await resetFormAfterEncryption() - await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain")) - } - } else { - if migration { - removePassphraseFromKeyChain() - } - await resetFormAfterEncryption() + private func encryptDatabaseAsync() async -> Bool { + await MainActor.run { + progressIndicator = true + } + do { + encryptionStartedDefault.set(true) + encryptionStartedAtDefault.set(Date.now) + if !m.chatDbChanged { + try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) + } + try await apiStorageEncryption(currentKey: currentKey, newKey: newKey) + encryptionStartedDefault.set(false) + initialRandomDBPassphraseGroupDefault.set(false) + if migration { + storeDBPassphraseGroupDefault.set(useKeychain) + } + if useKeychain { + if kcDatabasePassword.set(newKey) { + await resetFormAfterEncryption(true) await operationEnded(.databaseEncrypted) - } - } catch let error { - if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse { - await operationEnded(.currentPassphraseError) } else { - await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))")) + await resetFormAfterEncryption() + await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain")) } + } else { + if migration { + removePassphraseFromKeyChain() + } + await resetFormAfterEncryption() + await operationEnded(.databaseEncrypted) + } + return true + } catch let error { + if case .errorDatabase(.errorExport(.errorNotADatabase)) = error as? ChatError { + await operationEnded(.currentPassphraseError) + } else { + await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))")) + } + return false + } + } + + private func encryptDatabase() { + // it will try to stop and start the chat in case of: non-migration && successful encryption. In migration the chat will remain stopped + if migration { + Task { + await encryptDatabaseAsync() + } + } else { + stopChatRunBlockStartChat($progressIndicator) { + return await encryptDatabaseAsync() } } } @@ -371,6 +388,6 @@ func validKey(_ s: String) -> Bool { struct DatabaseEncryptionView_Previews: PreviewProvider { static var previews: some View { - DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false) + DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false, stopChatRunBlockStartChat: { _, _ in true }) } } diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift index 9d71e2a788..02a1b87826 100644 --- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift @@ -11,6 +11,7 @@ import SimpleXChat struct DatabaseErrorView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme @State var status: DBMigrationResult @State private var dbKey = "" @State private var storedDBKey = kcDatabasePassword.get() @@ -27,24 +28,40 @@ struct DatabaseErrorView: View { } } - @ViewBuilder private func databaseErrorView() -> some View { - VStack(alignment: .leading, spacing: 16) { + private func databaseErrorView() -> some View { + VStack(alignment: .center, spacing: 20) { switch status { case let .errorNotADatabase(dbFile): if useKeychain && storedDBKey != nil && storedDBKey != "" { titleText("Wrong database passphrase") Text("Database passphrase is different from saved in the keychain.") + .font(.callout) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + databaseKeyField(onSubmit: saveAndRunChat) - saveAndOpenButton() - fileNameText(dbFile) + Spacer() + VStack(spacing: 10) { + saveAndOpenButton() + fileNameText(dbFile) + } } else { titleText("Encrypted database") Text("Database passphrase is required to open chat.") + .font(.callout) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + .padding(.bottom, 5) + if useKeychain { databaseKeyField(onSubmit: saveAndRunChat) + Spacer() saveAndOpenButton() } else { databaseKeyField(onSubmit: { runChat() }) + Spacer() openChatButton() } } @@ -52,73 +69,105 @@ struct DatabaseErrorView: View { switch migrationError { case let .upgrade(upMigrations): titleText("Database upgrade") - Button("Upgrade and open chat") { runChat(confirmMigrations: .yesUp) } - fileNameText(dbFile) migrationsText(upMigrations.map(\.upName)) + Spacer() + VStack(spacing: 10) { + Button("Upgrade and open chat") { + runChat(confirmMigrations: .yesUp) + }.buttonStyle(OnboardingButtonStyle(isDisabled: false)) + fileNameText(dbFile) + } case let .downgrade(downMigrations): titleText("Database downgrade") - Text("Warning: you may lose some data!").bold() - Button("Downgrade and open chat") { runChat(confirmMigrations: .yesUpDown) } - fileNameText(dbFile) + Text("Warning: you may lose some data!") + .bold() + .padding(.horizontal, 25) + .multilineTextAlignment(.center) + migrationsText(downMigrations) + Spacer() + VStack(spacing: 10) { + Button("Downgrade and open chat") { + runChat(confirmMigrations: .yesUpDown) + }.buttonStyle(OnboardingButtonStyle(isDisabled: false)) + fileNameText(dbFile) + } case let .migrationError(mtrError): titleText("Incompatible database version") - fileNameText(dbFile) - Text("Error: ") + Text(mtrErrorDescription(mtrError)) + fileNameText(dbFile, font: .callout) + errorView(Text(mtrErrorDescription(mtrError))) } case let .errorSQL(dbFile, migrationSQLError): titleText("Database error") - fileNameText(dbFile) - Text("Error: \(migrationSQLError)") + fileNameText(dbFile, font: .callout) + errorView(Text("Error: \(migrationSQLError)")) case .errorKeychain: titleText("Keychain error") - Text("Cannot access keychain to save database password") + errorView(Text("Cannot access keychain to save database password")) case .invalidConfirmation: // this can only happen if incorrect parameter is passed - Text(String("Invalid migration confirmation")).font(.title) + titleText("Invalid migration confirmation") + errorView() + case let .unknown(json): titleText("Database error") - Text("Unknown database error: \(json)") + errorView(Text("Unknown database error: \(json)")) case .ok: EmptyView() } if showRestoreDbButton { - Spacer().frame(height: 10) + Spacer() Text("The attempt to change database passphrase was not completed.") + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + .font(.footnote) + restoreDbButton() } } - .padding() + .padding(.horizontal, 25) + .padding(.top, 75) + .padding(.bottom, 25) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear() { showRestoreDbButton = shouldShowRestoreDbButton() } } - private func titleText(_ s: LocalizedStringKey) -> Text { - Text(s).font(.title) + private func titleText(_ s: LocalizedStringKey) -> some View { + Text(s).font(.largeTitle).bold().multilineTextAlignment(.center) } - private func fileNameText(_ f: String) -> Text { - Text("File: \((f as NSString).lastPathComponent)") + private func fileNameText(_ f: String, font: Font = .caption) -> Text { + Text("File: \((f as NSString).lastPathComponent)").font(font) } - private func migrationsText(_ ms: [String]) -> Text { - Text("Migrations: \(ms.joined(separator: ", "))") + private func migrationsText(_ ms: [String]) -> some View { + (Text("Migrations:").font(.subheadline) + textNewLine + Text(ms.joined(separator: "\n")).font(.caption)) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) } private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View { PassphraseField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit) + .padding(.vertical, 10) + .padding(.horizontal) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color(uiColor: .tertiarySystemFill)) + ) } private func saveAndOpenButton() -> some View { Button("Save passphrase and open chat") { saveAndRunChat() } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } private func openChatButton() -> some View { Button("Open chat") { runChat() } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } private func saveAndRunChat() { @@ -192,8 +241,9 @@ struct DatabaseErrorView: View { secondaryButton: .cancel() )) } label: { - Text("Restore database backup").foregroundColor(.red) + Text("Restore database backup") } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } private func restoreDb() { @@ -208,6 +258,23 @@ struct DatabaseErrorView: View { )) } } + + private func errorView(_ s: Text? = nil) -> some View { + VStack(spacing: 35) { + Image(systemName: "exclamationmark.triangle.fill") + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.red) + + if let text = s { + text + .multilineTextAlignment(.center) + .font(.footnote) + } + } + .padding() + .frame(maxWidth: .infinity) + } } struct DatabaseErrorView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index f5b5287971..59eee1338b 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -44,8 +44,9 @@ enum DatabaseAlert: Identifiable { struct DatabaseView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Binding var showSettings: Bool + let dismissSettingsSheet: DismissAction @State private var runChat = false + @State private var stoppingChat = false @State private var alert: DatabaseAlert? = nil @State private var showFileImporter = false @State private var importedArchivePath: URL? @@ -57,6 +58,8 @@ struct DatabaseView: View { @State private var useKeychain = storeDBPassphraseGroupDefault.get() @State private var appFilesCountAndSize: (Int, Int)? + @State private var showDatabaseEncryptionView = false + @State var chatItemTTL: ChatItemTTL @State private var currentChatItemTTL: ChatItemTTL = .none @@ -69,7 +72,20 @@ struct DatabaseView: View { } } + @ViewBuilder private func chatDatabaseView() -> some View { + NavigationLink(isActive: $showDatabaseEncryptionView) { + DatabaseEncryptionView(useKeychain: $useKeychain, migration: false, stopChatRunBlockStartChat: { progressIndicator, block in + stopChatRunBlockStartChat(false, progressIndicator, block) + }) + .navigationTitle("Database passphrase") + .modifier(ThemedBackground(grouped: true)) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + List { let stopped = m.chatRunning == false Section { @@ -101,9 +117,10 @@ struct DatabaseView: View { isOn: $runChat ) .onChange(of: runChat) { _ in - if (runChat) { - startChat() - } else { + if runChat { + DatabaseView.startChat($runChat, $progressIndicator) + } else if !stoppingChat { + stoppingChat = false alert = .stopChat } } @@ -123,7 +140,9 @@ struct DatabaseView: View { let color: Color = unencrypted ? .orange : theme.colors.secondary settingsRow(unencrypted ? "lock.open" : useKeychain ? "key" : "lock", color: color) { NavigationLink { - DatabaseEncryptionView(useKeychain: $useKeychain, migration: false) + DatabaseEncryptionView(useKeychain: $useKeychain, migration: false, stopChatRunBlockStartChat: { progressIndicator, block in + stopChatRunBlockStartChat(false, progressIndicator, block) + }) .navigationTitle("Database passphrase") .modifier(ThemedBackground(grouped: true)) } label: { @@ -133,9 +152,14 @@ struct DatabaseView: View { settingsRow("square.and.arrow.up", color: theme.colors.secondary) { Button("Export database") { if initialRandomDBPassphraseGroupDefault.get() && !unencrypted { - alert = .exportProhibited + showDatabaseEncryptionView = true + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + alert = .exportProhibited + } } else { - exportArchive() + stopChatRunBlockStartChat(stopped, $progressIndicator) { + await exportArchive() + } } } } @@ -144,20 +168,6 @@ struct DatabaseView: View { showFileImporter = true } } - if let archiveName = chatArchiveName { - let title: LocalizedStringKey = chatArchiveTimeDefault.get() < chatLastStartGroupDefault.get() - ? "Old database archive" - : "New database archive" - settingsRow("archivebox", color: theme.colors.secondary) { - NavigationLink { - ChatArchiveView(archiveName: archiveName) - .navigationTitle(title) - .modifier(ThemedBackground(grouped: true)) - } label: { - Text(title) - } - } - } settingsRow("trash.slash", color: theme.colors.secondary) { Button("Delete database", role: .destructive) { alert = .deleteChat @@ -167,14 +177,10 @@ struct DatabaseView: View { Text("Chat database") .foregroundColor(theme.colors.secondary) } footer: { - Text( - stopped - ? "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." - : "Stop chat to enable database actions" - ) + Text("You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts.") .foregroundColor(theme.colors.secondary) } - .disabled(!stopped) + .disabled(progressIndicator) if case .group = dbContainer, legacyDatabase { Section(header: Text("Old database").foregroundColor(theme.colors.secondary)) { @@ -190,7 +196,7 @@ struct DatabaseView: View { Button(m.users.count > 1 ? "Delete files for all chat profiles" : "Delete all files", role: .destructive) { alert = .deleteFilesAndMedia } - .disabled(!stopped || appFilesCountAndSize?.0 == 0) + .disabled(progressIndicator || appFilesCountAndSize?.0 == 0) } header: { Text("Files & media") .foregroundColor(theme.colors.secondary) @@ -255,7 +261,9 @@ struct DatabaseView: View { title: Text("Import chat database?"), message: Text("Your current chat database will be DELETED and REPLACED with the imported one.") + Text("This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost."), primaryButton: .destructive(Text("Import")) { - importArchive(fileURL) + stopChatRunBlockStartChat(m.chatRunning == false, $progressIndicator) { + await DatabaseView.importArchive(fileURL, $progressIndicator, $alert, false) + } }, secondaryButton: .cancel() ) @@ -263,19 +271,15 @@ struct DatabaseView: View { return Alert(title: Text("Error: no database file")) } case .archiveImported: - return Alert( - title: Text("Chat database imported"), - message: Text("Restart the app to use imported chat database") - ) + let (title, message) = archiveImportedAlertText() + return Alert(title: Text(title), message: Text(message)) case let .archiveImportedWithErrors(errs): - return Alert( - title: Text("Chat database imported"), - message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs) - ) + let (title, message) = archiveImportedWithErrorsAlertText(errs: errs) + return Alert(title: Text(title), message: Text(message)) case let .archiveExportedWithErrors(archivePath, errs): return Alert( title: Text("Chat database exported"), - message: Text("You may save the exported archive.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), + message: Text("You may save the exported archive.") + textNewLine + Text("Some file(s) were not exported:") + Text(archiveErrorsText(errs)), dismissButton: .default(Text("Continue")) { showShareSheet(items: [archivePath]) } @@ -285,15 +289,17 @@ struct DatabaseView: View { title: Text("Delete chat profile?"), message: Text("This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost."), primaryButton: .destructive(Text("Delete")) { - deleteChat() + let wasStopped = m.chatRunning == false + stopChatRunBlockStartChat(wasStopped, $progressIndicator) { + _ = await deleteChat() + return true + } }, secondaryButton: .cancel() ) case .chatDeleted: - return Alert( - title: Text("Chat database deleted"), - message: Text("Restart the app to create a new chat profile") - ) + let (title, message) = chatDeletedAlertText() + return Alert(title: Text(title), message: Text(message)) case .deleteLegacyDatabase: return Alert( title: Text("Delete old database?"), @@ -308,7 +314,10 @@ struct DatabaseView: View { title: Text("Delete files and media?"), message: Text("This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain."), primaryButton: .destructive(Text("Delete")) { - deleteFiles() + stopChatRunBlockStartChat(m.chatRunning == false, $progressIndicator) { + deleteFiles() + return true + } }, secondaryButton: .cancel() ) @@ -328,95 +337,184 @@ struct DatabaseView: View { } } - private func authStopChat() { + private func authStopChat(_ onStop: (() -> Void)? = nil) { if UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) { authenticate(reason: NSLocalizedString("Stop SimpleX", comment: "authentication reason")) { laResult in switch laResult { - case .success: stopChat() - case .unavailable: stopChat() + case .success: stopChat(onStop) + case .unavailable: stopChat(onStop) case .failed: withAnimation { runChat = true } } } } else { - stopChat() + stopChat(onStop) } } - private func stopChat() { + private func stopChat(_ onStop: (() -> Void)? = nil) { Task { do { try await stopChatAsync() + onStop?() } catch let error { await MainActor.run { runChat = true - alert = .error(title: "Error stopping chat", error: responseError(error)) + showAlert("Error stopping chat", message: responseError(error)) } } } } - private func exportArchive() { - progressIndicator = true - Task { - do { - let (archivePath, archiveErrors) = try await exportChatArchive() - if archiveErrors.isEmpty { - showShareSheet(items: [archivePath]) - await MainActor.run { progressIndicator = false } - } else { - await MainActor.run { - alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors) - progressIndicator = false + func stopChatRunBlockStartChat( + _ stopped: Bool, + _ progressIndicator: Binding, + _ block: @escaping () async throws -> Bool + ) { + // if the chat was running, the sequence is: stop chat, run block, start chat. + // Otherwise, just run block and do nothing - the toggle will be visible anyway and the user can start the chat or not + if stopped { + Task { + do { + _ = try await block() + } catch { + logger.error("Error while executing block: \(error)") + } + } + } else { + authStopChat { + stoppingChat = true + runChat = false + Task { + // if it throws, let's start chat again anyway + var canStart = false + do { + canStart = try await block() + } catch { + logger.error("Error executing block: \(error)") + canStart = true + } + if canStart { + await MainActor.run { + DatabaseView.startChat($runChat, $progressIndicator) + } } } + } + } + } + + static func startChat(_ runChat: Binding, _ progressIndicator: Binding) { + progressIndicator.wrappedValue = true + let m = ChatModel.shared + if m.chatDbChanged { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + resetChatCtrl() + do { + let hadDatabase = hasDatabase() + try initializeChat(start: true) + m.chatDbChanged = false + AppChatState.shared.set(.active) + if m.chatDbStatus != .ok || !hadDatabase { + // Hide current view and show `DatabaseErrorView` + dismissAllSheets(animated: true) + } + } catch let error { + fatalError("Error starting chat \(responseError(error))") + } + progressIndicator.wrappedValue = false + } + } else { + do { + _ = try apiStartChat() + runChat.wrappedValue = true + m.chatRunning = true + ChatReceiver.shared.start() + chatLastStartGroupDefault.set(Date.now) + AppChatState.shared.set(.active) } catch let error { + runChat.wrappedValue = false + showAlert(NSLocalizedString("Error starting chat", comment: ""), message: responseError(error)) + } + progressIndicator.wrappedValue = false + } + } + + private func exportArchive() async -> Bool { + await MainActor.run { + progressIndicator = true + } + do { + let (archivePath, archiveErrors) = try await exportChatArchive() + if archiveErrors.isEmpty { + showShareSheet(items: [archivePath]) + await MainActor.run { progressIndicator = false } + } else { await MainActor.run { - alert = .error(title: "Error exporting chat database", error: responseError(error)) + alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors) progressIndicator = false } } + } catch let error { + await MainActor.run { + alert = .error(title: "Error exporting chat database", error: responseError(error)) + progressIndicator = false + } } + return false } - private func importArchive(_ archivePath: URL) { + static func importArchive( + _ archivePath: URL, + _ progressIndicator: Binding, + _ alert: Binding, + _ migration: Bool + ) async -> Bool { if archivePath.startAccessingSecurityScopedResource() { - progressIndicator = true - Task { - do { - try await apiDeleteStorage() - try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) - do { - let config = ArchiveConfig(archivePath: archivePath.path) - let archiveErrors = try await apiImportArchive(config: config) - _ = kcDatabasePassword.remove() - if archiveErrors.isEmpty { - await operationEnded(.archiveImported) - } else { - await operationEnded(.archiveImportedWithErrors(archiveErrors: archiveErrors)) - } - } catch let error { - await operationEnded(.error(title: "Error importing chat database", error: responseError(error))) - } - } catch let error { - await operationEnded(.error(title: "Error deleting chat database", error: responseError(error))) - } + defer { archivePath.stopAccessingSecurityScopedResource() } + await MainActor.run { + progressIndicator.wrappedValue = true + } + do { + try await apiDeleteStorage() + try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) + do { + let config = ArchiveConfig(archivePath: archivePath.path) + let archiveErrors = try await apiImportArchive(config: config) + shouldImportAppSettingsDefault.set(true) + _ = kcDatabasePassword.remove() + if archiveErrors.isEmpty { + await operationEnded(.archiveImported, progressIndicator, alert) + return true + } else { + await operationEnded(.archiveImportedWithErrors(archiveErrors: archiveErrors), progressIndicator, alert) + return migration + } + } catch let error { + await operationEnded(.error(title: "Error importing chat database", error: responseError(error)), progressIndicator, alert) + } + } catch let error { + await operationEnded(.error(title: "Error deleting chat database", error: responseError(error)), progressIndicator, alert) + } } else { - alert = .error(title: "Error accessing database file") + showAlert("Error accessing database file") } + return false } - private func deleteChat() { - progressIndicator = true - Task { - do { - try await deleteChatAsync() - await operationEnded(.chatDeleted) - appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) - } catch let error { - await operationEnded(.error(title: "Error deleting database", error: responseError(error))) - } + private func deleteChat() async -> Bool { + await MainActor.run { + progressIndicator = true + } + do { + try await deleteChatAsync() + appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) + await DatabaseView.operationEnded(.chatDeleted, $progressIndicator, $alert) + return true + } catch let error { + await DatabaseView.operationEnded(.error(title: "Error deleting database", error: responseError(error)), $progressIndicator, $alert) + return false } } @@ -428,39 +526,30 @@ struct DatabaseView: View { } } - private func operationEnded(_ dbAlert: DatabaseAlert) async { + private static func operationEnded(_ dbAlert: DatabaseAlert, _ progressIndicator: Binding, _ alert: Binding) async { await MainActor.run { + let m = ChatModel.shared m.chatDbChanged = true m.chatInitialized = false - progressIndicator = false - alert = dbAlert + progressIndicator.wrappedValue = false } - } - - private func startChat() { - if m.chatDbChanged { - showSettings = false - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - resetChatCtrl() - do { - try initializeChat(start: true) - m.chatDbChanged = false - AppChatState.shared.set(.active) - } catch let error { - fatalError("Error starting chat \(responseError(error))") - } - } - } else { - do { - _ = try apiStartChat() - runChat = true - m.chatRunning = true - ChatReceiver.shared.start() - chatLastStartGroupDefault.set(Date.now) - AppChatState.shared.set(.active) - } catch let error { - runChat = false - alert = .error(title: "Error starting chat", error: responseError(error)) + await withCheckedContinuation { cont in + let okAlertActionWaiting = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default, handler: { _ in cont.resume() }) + // show these alerts globally so they are visible when all sheets will be hidden + if case .archiveImported = dbAlert { + let (title, message) = archiveImportedAlertText() + showAlert(title, message: message, actions: { [okAlertActionWaiting] }) + } else if case .archiveImportedWithErrors(let errs) = dbAlert { + let (title, message) = archiveImportedWithErrorsAlertText(errs: errs) + showAlert(title, message: message, actions: { [okAlertActionWaiting] }) + } else if case .chatDeleted = dbAlert { + let (title, message) = chatDeletedAlertText() + showAlert(title, message: message, actions: { [okAlertActionWaiting] }) + } else if case let .error(title, error) = dbAlert { + showAlert("\(title)", message: error, actions: { [okAlertActionWaiting] }) + } else { + alert.wrappedValue = dbAlert + cont.resume() } } } @@ -503,8 +592,28 @@ struct DatabaseView: View { } } -func archiveErrorsText(_ errs: [ArchiveError]) -> Text { - return Text("\n" + errs.map(showArchiveError).joined(separator: "\n")) +func archiveImportedAlertText() -> (String, String) { + ( + NSLocalizedString("Chat database imported", comment: ""), + NSLocalizedString("Restart the app to use imported chat database", comment: "") + ) +} +func archiveImportedWithErrorsAlertText(errs: [ArchiveError]) -> (String, String) { + ( + NSLocalizedString("Chat database imported", comment: ""), + NSLocalizedString("Restart the app to use imported chat database", comment: "") + "\n" + NSLocalizedString("Some non-fatal errors occurred during import:", comment: "") + archiveErrorsText(errs) + ) +} + +private func chatDeletedAlertText() -> (String, String) { + ( + NSLocalizedString("Chat database deleted", comment: ""), + NSLocalizedString("Restart the app to create a new chat profile", comment: "") + ) +} + +func archiveErrorsText(_ errs: [ArchiveError]) -> String { + return "\n" + errs.map(showArchiveError).joined(separator: "\n") func showArchiveError(_ err: ArchiveError) -> String { switch err { @@ -533,7 +642,9 @@ func deleteChatAsync() async throws { } struct DatabaseView_Previews: PreviewProvider { + @Environment(\.dismiss) static var mockDismiss + static var previews: some View { - DatabaseView(showSettings: Binding.constant(false), chatItemTTL: .none) + DatabaseView(dismissSettingsSheet: mockDismiss, chatItemTTL: .none) } } diff --git a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift index e79f24c6d9..79c0a42ae0 100644 --- a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift +++ b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift @@ -117,7 +117,7 @@ struct MigrateToAppGroupView: View { setV3DBMigration(.migration_error) migrationError = "Error starting chat: \(responseError(error))" } - deleteOldArchive() + deleteOldChatArchive() } label: { Text("Start chat") .font(.title) @@ -235,14 +235,16 @@ func exportChatArchive(_ storagePath: URL? = nil) async throws -> (URL, [Archive try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) let errs = try await apiExportArchive(config: config) if storagePath == nil { - deleteOldArchive() + deleteOldChatArchive() UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME) chatArchiveTimeDefault.set(archiveTime) } return (archivePath, errs) } -func deleteOldArchive() { +/// Deprecated. Remove in the end of 2025. All unused archives should be deleted for the most users til then. +/// Remove DEFAULT_CHAT_ARCHIVE_NAME and DEFAULT_CHAT_ARCHIVE_TIME as well +func deleteOldChatArchive() { let d = UserDefaults.standard if let archiveName = d.string(forKey: DEFAULT_CHAT_ARCHIVE_NAME) { do { diff --git a/apps/ios/Shared/Views/Helpers/AppSheet.swift b/apps/ios/Shared/Views/Helpers/AppSheet.swift index 0ade1c0d8e..1e334367e8 100644 --- a/apps/ios/Shared/Views/Helpers/AppSheet.swift +++ b/apps/ios/Shared/Views/Helpers/AppSheet.swift @@ -11,6 +11,12 @@ import SwiftUI class AppSheetState: ObservableObject { static let shared = AppSheetState() @Published var scenePhaseActive: Bool = false + + func redactionReasons(_ protectScreen: Bool) -> RedactionReasons { + !protectScreen || scenePhaseActive + ? RedactionReasons() + : RedactionReasons.placeholder + } } private struct PrivacySensitive: ViewModifier { @@ -19,11 +25,7 @@ private struct PrivacySensitive: ViewModifier { @ObservedObject var appSheetState: AppSheetState = AppSheetState.shared func body(content: Content) -> some View { - if !protectScreen { - content - } else { - content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy) - } + content.redacted(reason: appSheetState.redactionReasons(protectScreen)) } } diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index 477dc567eb..9aa6ac86cf 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -14,50 +14,55 @@ import SimpleXChat /// Supports [Dynamic Type](https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically) /// by retaining pill shape, even when ``ChatItem``'s height is less that twice its corner radius struct ChatItemClipped: ViewModifier { - struct ClipShape: Shape { - let maxCornerRadius: Double - - func path(in rect: CGRect) -> Path { - Path( - roundedRect: rect, - cornerRadius: min((rect.height / 2), maxCornerRadius), - style: .circular - ) - } - } - + @AppStorage(DEFAULT_CHAT_ITEM_ROUNDNESS) private var roundness = defaultChatItemRoundness + @AppStorage(DEFAULT_CHAT_ITEM_TAIL) private var tailEnabled = true + private let chatItem: (content: CIContent, chatDir: CIDirection)? + private let tailVisible: Bool + init() { - clipShape = ClipShape( - maxCornerRadius: 18 - ) + self.chatItem = nil + self.tailVisible = false + } + + init(_ ci: ChatItem, tailVisible: Bool) { + self.chatItem = (ci.content, ci.chatDir) + self.tailVisible = tailVisible } - init(_ chatItem: ChatItem) { - clipShape = ClipShape( - maxCornerRadius: { - switch chatItem.content { - case - .sndMsgContent, + private func shapeStyle() -> ChatItemShape.Style { + if let ci = chatItem { + switch ci.content { + case + .sndMsgContent, .rcvMsgContent, .rcvDecryptionError, - .rcvGroupInvitation, - .sndGroupInvitation, - .sndDeleted, - .rcvDeleted, .rcvIntegrityError, - .sndModerated, - .rcvModerated, - .rcvBlocked, - .invalidJSON: 18 - default: 8 + .invalidJSON: + let tail = if let mc = ci.content.msgContent, mc.isImageOrVideo && mc.text.isEmpty { + false + } else { + tailVisible } - }() - ) + return tailEnabled + ? .bubble( + padding: ci.chatDir.sent ? .trailing : .leading, + tailVisible: tail + ) + : .roundRect(radius: msgRectMaxRadius) + case .rcvGroupInvitation, .sndGroupInvitation: + return .roundRect(radius: msgRectMaxRadius) + default: return .roundRect(radius: 8) + } + } else { + return .roundRect(radius: msgRectMaxRadius) + } } - - private let clipShape: ClipShape - + func body(content: Content) -> some View { + let clipShape = ChatItemShape( + roundness: roundness, + style: shapeStyle() + ) content .contentShape(.dragPreview, clipShape) .contentShape(.contextMenuPreview, clipShape) @@ -65,4 +70,106 @@ struct ChatItemClipped: ViewModifier { } } +struct ChatTailPadding: ViewModifier { + func body(content: Content) -> some View { + content.padding(.horizontal, -msgTailWidth) + } +} +private let msgRectMaxRadius: Double = 18 +private let msgBubbleMaxRadius: Double = msgRectMaxRadius * 1.2 +private let msgTailWidth: Double = 9 +private let msgTailMinHeight: Double = msgTailWidth * 1.254 // ~56deg +private let msgTailMaxHeight: Double = msgTailWidth * 1.732 // 60deg + +struct ChatItemShape: Shape { + fileprivate enum Style { + case bubble(padding: HorizontalEdge, tailVisible: Bool) + case roundRect(radius: Double) + } + + fileprivate let roundness: Double + fileprivate let style: Style + + func path(in rect: CGRect) -> Path { + switch style { + case let .bubble(padding, tailVisible): + let w = rect.width + let h = rect.height + let rxMax = min(msgBubbleMaxRadius, w / 2) + let ryMax = min(msgBubbleMaxRadius, h / 2) + let rx = roundness * rxMax + let ry = roundness * ryMax + let tailHeight = min(msgTailMinHeight + roundness * (msgTailMaxHeight - msgTailMinHeight), h / 2) + var path = Path() + // top side + path.move(to: CGPoint(x: rx, y: 0)) + path.addLine(to: CGPoint(x: w - rx, y: 0)) + if roundness > 0 { + // top-right corner + path.addQuadCurve(to: CGPoint(x: w, y: ry), control: CGPoint(x: w, y: 0)) + } + if rect.height > 2 * ry { + // right side + path.addLine(to: CGPoint(x: w, y: h - ry)) + } + if roundness > 0 { + // bottom-right corner + path.addQuadCurve(to: CGPoint(x: w - rx, y: h), control: CGPoint(x: w, y: h)) + } + // bottom side + if tailVisible { + path.addLine(to: CGPoint(x: -msgTailWidth, y: h)) + if roundness > 0 { + // bottom-left tail + // distance of control point from touch point, calculated via ratios + let d = tailHeight - msgTailWidth * msgTailWidth / tailHeight + // tail control point + let tc = CGPoint(x: 0, y: h - tailHeight + d * sqrt(roundness)) + // bottom-left tail curve + path.addQuadCurve(to: CGPoint(x: 0, y: h - tailHeight), control: tc) + } else { + path.addLine(to: CGPoint(x: 0, y: h - tailHeight)) + } + if rect.height > ry + tailHeight { + // left side + path.addLine(to: CGPoint(x: 0, y: ry)) + } + } else { + path.addLine(to: CGPoint(x: rx, y: h)) + path.addQuadCurve(to: CGPoint(x: 0, y: h - ry), control: CGPoint(x: 0 , y: h)) + if rect.height > 2 * ry { + // left side + path.addLine(to: CGPoint(x: 0, y: ry)) + } + } + if roundness > 0 { + // top-left corner + path.addQuadCurve(to: CGPoint(x: rx, y: 0), control: CGPoint(x: 0, y: 0)) + } + path.closeSubpath() + return switch padding { + case .leading: path + case .trailing: path + .scale(x: -1, y: 1, anchor: .center) + .path(in: rect) + } + case let .roundRect(radius): + return Path(roundedRect: rect, cornerRadius: radius * roundness) + } + } + + var offset: Double? { + switch style { + case let .bubble(padding, isTailVisible): + if isTailVisible { + switch padding { + case .leading: -msgTailWidth + case .trailing: msgTailWidth + } + } else { 0 } + case .roundRect: 0 + } + } + +} diff --git a/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift b/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift new file mode 100644 index 0000000000..dca413dafe --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift @@ -0,0 +1,21 @@ +// +// Test.swift +// SimpleX (iOS) +// +// Created by Levitating Pineapple on 31/08/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +extension View { + @ViewBuilder + func invertedForegroundStyle(enabled: Bool = true) -> some View { + if enabled { + foregroundStyle(Material.ultraThin) + .environment(\.colorScheme, .dark) + .grayscale(1) + .contrast(-20) + } else { self } + } +} diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift index 248504c59b..3eedd56441 100644 --- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift +++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift @@ -20,7 +20,7 @@ struct ProfileImage: View { @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner var body: some View { - if let uiImage = UIImage(base64Encoded: imageStr) { + if let uiImage = imageFromBase64(imageStr) { clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred) } else { let c = color.asAnotherColorFromSecondaryVariant(theme) diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 936c6cb3ab..b8de0e4ceb 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -8,15 +8,63 @@ import SwiftUI -func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { +func getTopViewController() -> UIViewController? { let keyWindowScene = UIApplication.shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene if let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first, - let presentedViewController = keyWindow.rootViewController?.presentedViewController ?? keyWindow.rootViewController { + let rootViewController = keyWindow.rootViewController { + // Find the top-most presented view controller + var topController = rootViewController + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + return topController + } + return nil +} + +func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { + if let topController = getTopViewController() { let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) if let completed = completed { - let handler: UIActivityViewController.CompletionWithItemsHandler = { _,_,_,_ in completed() } - activityViewController.completionWithItemsHandler = handler - } - presentedViewController.present(activityViewController, animated: true) + activityViewController.completionWithItemsHandler = { _, _, _, _ in + completed() + } + } + topController.present(activityViewController, animated: true) } } + +func showAlert( + title: String, + message: String? = nil, + buttonTitle: String, + buttonAction: @escaping () -> Void, + cancelButton: Bool +) -> Void { + if let topController = getTopViewController() { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: buttonTitle, style: .default) { _ in + buttonAction() + }) + if cancelButton { + alert.addAction(cancelAlertAction) + } + topController.present(alert, animated: true) + } +} + +func showAlert( + _ title: String, + message: String? = nil, + actions: () -> [UIAlertAction] = { [okAlertAction] } +) { + if let topController = getTopViewController() { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + for action in actions() { alert.addAction(action) } + topController.present(alert, animated: true) + } +} + +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/Helpers/SheetRepresentable.swift b/apps/ios/Shared/Views/Helpers/SheetRepresentable.swift new file mode 100644 index 0000000000..841d5c7eda --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/SheetRepresentable.swift @@ -0,0 +1,188 @@ +// +// SwiftUISheet.swift +// SimpleX (iOS) +// +// Created by user on 23/09/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +private let sheetAnimationDuration: Double = 0.35 + +// Refrence: https://easings.net/ +private let easeOutCubic = UICubicTimingParameters( + controlPoint1: CGPoint(x: 0.215, y: 0.61), + controlPoint2: CGPoint(x: 0.355, y: 1) +) + +struct Sheet: ViewModifier { + @Binding var isPresented: Bool + @ViewBuilder let sheetContent: () -> SheetContent + + func body(content: Content) -> some View { + ZStack { + content + SheetRepresentable(isPresented: $isPresented, content: sheetContent()) + .allowsHitTesting(isPresented) + .ignoresSafeArea() + } + } +} + +struct SheetRepresentable: UIViewControllerRepresentable { + @Binding var isPresented: Bool + let content: Content + + func makeUIViewController(context: Context) -> Controller { + Controller(content: content, representer: self) + } + + func updateUIViewController(_ sheetController: Controller, context: Context) { + sheetController.animate(isPresented: isPresented) + } + + class Controller: UIViewController { + let hostingController: UIHostingController + private let animator = UIViewPropertyAnimator( + duration: sheetAnimationDuration, + timingParameters: easeOutCubic + ) + private let representer: SheetRepresentable + private var retainedFraction: CGFloat = 0 + private var sheetHeight: Double { hostingController.view.frame.height } + private var task: Task? + + init(content: C, representer: SheetRepresentable) { + self.representer = representer + self.hostingController = UIHostingController(rootView: content) + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) missing") } + + deinit { + animator.stopAnimation(true) + animator.finishAnimation(at: .current) + } + + func animate(isPresented: Bool) { + let alreadyAnimating = animator.isRunning && isPresented != animator.isReversed + let sheetFullyDismissed = animator.fractionComplete == (animator.isReversed ? 1 : 0) + let sheetFullyPresented = animator.fractionComplete == (animator.isReversed ? 0 : 1) + + if !isPresented && sheetFullyDismissed || + isPresented && sheetFullyPresented || + alreadyAnimating { + return + } + + animator.pauseAnimation() + animator.isReversed = !isPresented + animator.continueAnimation( + withTimingParameters: isPresented + ? easeOutCubic + : UICubicTimingParameters(animationCurve: .easeIn), + durationFactor: 1 - animator.fractionComplete + ) + handleVisibility() + } + + func handleVisibility() { + if animator.isReversed { + task = Task { + do { + let sleepDuration = UInt64(sheetAnimationDuration * Double(NSEC_PER_SEC)) + try await Task.sleep(nanoseconds: sleepDuration) + view.isHidden = true + } catch { } + } + } else { + task?.cancel() + task = nil + view.isHidden = false + } + } + + override func viewDidLoad() { + view.isHidden = true + view.backgroundColor = .clear + view.addGestureRecognizer( + UITapGestureRecognizer(target: self, action: #selector(tap(gesture:))) + ) + addChild(hostingController) + hostingController.didMove(toParent: self) + if let sheet = hostingController.view { + sheet.isHidden = true + sheet.clipsToBounds = true + sheet.layer.cornerRadius = 10 + sheet.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + sheet.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan(gesture:)))) + sheet.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(sheet) + NSLayoutConstraint.activate([ + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + } + + override func viewDidAppear(_ animated: Bool) { + // Ensures animations are only setup once + // on some iOS version `viewDidAppear` can get called on each state change. + if hostingController.view.isHidden { + hostingController.view.transform = CGAffineTransform(translationX: 0, y: self.sheetHeight) + hostingController.view.isHidden = false + animator.pausesOnCompletion = true + animator.addAnimations { + self.hostingController.view.transform = .identity + self.view.backgroundColor = UIColor { + switch $0.userInterfaceStyle { + case .dark: .black.withAlphaComponent(0.290) + default: .black.withAlphaComponent(0.121) + } + } + } + animator.startAnimation() + animator.pauseAnimation() + } + } + + @objc + func pan(gesture: UIPanGestureRecognizer) { + switch gesture.state { + case .began: + animator.isReversed = false + animator.pauseAnimation() + retainedFraction = animator.fractionComplete + case .changed: + animator.fractionComplete = retainedFraction - gesture.translation(in: view).y / sheetHeight + case .ended, .cancelled: + let velocity = gesture.velocity(in: view).y + animator.isReversed = (velocity - (animator.fractionComplete - 0.5) * 100).sign == .plus + let defaultVelocity = sheetHeight / sheetAnimationDuration + let fractionRemaining = 1 - animator.fractionComplete + let durationFactor = min(max(fractionRemaining / (abs(velocity) / defaultVelocity), 0.5), 1) + animator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor * fractionRemaining) + handleVisibility() + DispatchQueue.main.asyncAfter(deadline: .now() + sheetAnimationDuration) { + self.representer.isPresented = !self.animator.isReversed + } + default: break + } + } + + @objc + func tap(gesture: UITapGestureRecognizer) { + switch gesture.state { + case .ended: + if gesture.location(in: view).y < view.frame.height - sheetHeight { + representer.isPresented = false + } + default: break + } + } + } +} diff --git a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift new file mode 100644 index 0000000000..5799962778 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift @@ -0,0 +1,61 @@ +// +// StickyScrollView.swift +// SimpleX (iOS) +// +// Created by user on 20/09/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct StickyScrollView: UIViewRepresentable { + @Binding var resetScroll: ResetScrollAction + @ViewBuilder let content: () -> Content + + func makeUIView(context: Context) -> UIScrollView { + let hc = context.coordinator.hostingController + hc.view.backgroundColor = .clear + let sv = UIScrollView() + sv.showsHorizontalScrollIndicator = false + sv.addSubview(hc.view) + sv.delegate = context.coordinator + DispatchQueue.main.async { + resetScroll = ResetScrollAction { sv.setContentOffset(.zero, animated: false) } + } + return sv + } + + func updateUIView(_ scrollView: UIScrollView, context: Context) { + let hc = context.coordinator.hostingController + hc.rootView = content() + hc.view.frame.size = hc.view.intrinsicContentSize + scrollView.contentSize = hc.view.intrinsicContentSize + } + + func makeCoordinator() -> Coordinator { + Coordinator(content: content()) + } + + class Coordinator: NSObject, UIScrollViewDelegate { + let hostingController: UIHostingController + + init(content: Content) { + self.hostingController = UIHostingController(rootView: content) + } + + func scrollViewWillEndDragging( + _ scrollView: UIScrollView, + withVelocity velocity: CGPoint, + targetContentOffset: UnsafeMutablePointer + ) { + if targetContentOffset.pointee.x < 32 { + targetContentOffset.pointee.x = 0 + } + } + } +} + +struct ResetScrollAction { + var action = { } + func callAsFunction() { action() } +} diff --git a/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift b/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift index ae94b4685c..9d5ae2e289 100644 --- a/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift +++ b/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift @@ -16,6 +16,7 @@ struct UserWallpaperEditor: View { @State var themeModeOverride: ThemeModeOverride @State var applyToMode: DefaultThemeMode? @State var showMore: Bool = false + @State var showFileImporter: Bool = false @Binding var globalThemeUsed: Bool var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void @@ -125,24 +126,27 @@ struct UserWallpaperEditor: View { CustomizeThemeColorsSection(editColor: { name in editColor(name, theme) }) - ImportExportThemeSection(perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in - let importedFromString = imported.wallpaper?.importFromString() - let importedType = importedFromString?.toAppWallpaper().type - let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get()) - let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } - let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors - let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) - Task { - await MainActor.run { - themeModeOverride = res - } - await save(applyToMode, res) - } - } + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) } else { AdvancedSettingsButton(theme.colors.primary) { showMore = true } } } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { imported in + let importedFromString = imported.wallpaper?.importFromString() + let importedType = importedFromString?.toAppWallpaper().type + let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get()) + let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } + let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors + let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) + Task { + await MainActor.run { + themeModeOverride = res + } + await save(applyToMode, res) + } + } + ) } private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool { @@ -216,6 +220,7 @@ struct ChatWallpaperEditor: View { @State var themeModeOverride: ThemeModeOverride @State var applyToMode: DefaultThemeMode? @State var showMore: Bool = false + @State var showFileImporter: Bool = false @Binding var globalThemeUsed: Bool var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void @@ -328,24 +333,27 @@ struct ChatWallpaperEditor: View { CustomizeThemeColorsSection(editColor: editColor) - ImportExportThemeSection(perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in - let importedFromString = imported.wallpaper?.importFromString() - let importedType = importedFromString?.toAppWallpaper().type - let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) - let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } - let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors - let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) - Task { - await MainActor.run { - themeModeOverride = res - } - await save(applyToMode, res) - } - } + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) } else { AdvancedSettingsButton(theme.colors.primary) { showMore = true } } } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { imported in + let importedFromString = imported.wallpaper?.importFromString() + let importedType = importedFromString?.toAppWallpaper().type + let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) + let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } + let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors + let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) + Task { + await MainActor.run { + themeModeOverride = res + } + await save(applyToMode, res) + } + } + ) } private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool { diff --git a/apps/ios/Shared/Views/Helpers/UserDefault.swift b/apps/ios/Shared/Views/Helpers/UserDefault.swift new file mode 100644 index 0000000000..5f18465d20 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserDefault.swift @@ -0,0 +1,62 @@ +// +// UserDefault.swift +// SimpleX (iOS) +// +// Created by user on 14/10/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import Combine + +@propertyWrapper +public struct UserDefault: DynamicProperty { + @StateObject private var observer = UserDefaultObserver() + let initialValue: Value + let key: String + let store: UserDefaults + + public init( + wrappedValue: Value, + _ key: String, + store: UserDefaults = .standard + ) { + self.initialValue = wrappedValue + self.key = key + self.store = store + } + + public var wrappedValue: Value { + get { + // Observer can only be accessed after the property wrapper is installed in view (runtime exception) + observer.subscribe(to: key) + return store.object(forKey: key) as? Value ?? initialValue + } + nonmutating set { + store.set(newValue, forKey: key) + } + } +} + +private class UserDefaultObserver: ObservableObject { + private var subscribed = false + + func subscribe(to key: String) { + if !subscribed { + NotificationCenter.default.addObserver( + self, + selector: #selector(userDefaultsDidChange), + name: UserDefaults.didChangeNotification, + object: nil + ) + subscribed = true + } + } + + @objc + private func userDefaultsDidChange(_ notification: Notification) { + Task { @MainActor in objectWillChange.send() } + } + + deinit { NotificationCenter.default.removeObserver(self) } +} diff --git a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift index c790b9cff2..85ef85c611 100644 --- a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift +++ b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift @@ -9,6 +9,7 @@ import SwiftUI extension View { + @inline(__always) @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) @@ -36,9 +37,9 @@ struct PrivacyBlur: ViewModifier { .overlay { if (blurred && enabled) { Color.clear.contentShape(Rectangle()) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { blurred = false - } + }) } } .onReceive(NotificationCenter.default.publisher(for: .chatViewWillBeginScrolling)) { _ in diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift index 27bb95b599..16ab26eff7 100644 --- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift +++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift @@ -65,6 +65,7 @@ struct LocalAuthView: View { // Clear sensitive data on screen just in case app fails to hide its views while new database is created m.chatId = nil ItemsModel.shared.reversedChatItems = [] + ItemsModel.shared.chatState.clear() m.updateChats([]) m.users = [] _ = kcAppPassword.set(password) diff --git a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift index 609943bcb6..4a6f8e7549 100644 --- a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift +++ b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift @@ -28,7 +28,7 @@ struct PasscodeEntry: View { } } - @ViewBuilder private func passwordView() -> some View { + private func passwordView() -> some View { Text( password == "" ? " " diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 9cc229ba80..0af8fa7ad8 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -24,6 +24,7 @@ private enum MigrationFromState: Equatable { } private enum MigrateFromDeviceViewAlert: Identifiable { + case finishMigration(_ fileId: Int64, _ ctrl: chat_ctrl) case deleteChat(_ title: LocalizedStringKey = "Delete chat profile?", _ text: LocalizedStringKey = "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.") case startChat(_ title: LocalizedStringKey = "Start chat?", _ text: LocalizedStringKey = "Warning: starting chat on multiple devices is not supported and will cause message delivery failures") @@ -38,6 +39,7 @@ private enum MigrateFromDeviceViewAlert: Identifiable { var id: String { switch self { + case .finishMigration: return "finishMigration" case let .deleteChat(title, text): return "\(title) \(text)" case let .startChat(title, text): return "\(title) \(text)" @@ -56,8 +58,6 @@ private enum MigrateFromDeviceViewAlert: Identifiable { struct MigrateFromDevice: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Environment(\.dismiss) var dismiss: DismissAction - @Binding var showSettings: Bool @Binding var showProgressOnSettings: Bool @State private var migrationState: MigrationFromState = .chatStopInProgress @State private var useKeychain = storeDBPassphraseGroupDefault.get() @@ -106,9 +106,6 @@ struct MigrateFromDevice: View { finishedView(chatDeletion) } } - .modifier(BackButton(label: "Back", disabled: $backDisabled) { - dismiss() - }) .onChange(of: migrationState) { state in backDisabled = switch migrationState { case .chatStopInProgress, .archiving, .linkShown, .finished: true @@ -138,6 +135,15 @@ struct MigrateFromDevice: View { } .alert(item: $alert) { alert in switch alert { + case let .finishMigration(fileId, ctrl): + return Alert( + title: Text("Remove archive?"), + message: Text("The uploaded database archive will be permanently removed from the servers."), + primaryButton: .destructive(Text("Continue")) { + finishMigration(fileId, ctrl) + }, + secondaryButton: .cancel() + ) case let .startChat(title, text): return Alert( title: Text(title), @@ -171,7 +177,7 @@ struct MigrateFromDevice: View { case let .archiveExportedWithErrors(archivePath, errs): return Alert( title: Text("Chat database exported"), - message: Text("You may migrate the exported database.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), + message: Text("You may migrate the exported database.") + textNewLine + Text("Some file(s) were not exported:") + Text(archiveErrorsText(errs)), dismissButton: .default(Text("Continue")) { Task { await uploadArchive(path: archivePath) } } @@ -216,7 +222,8 @@ struct MigrateFromDevice: View { } private func passphraseNotSetView() -> some View { - DatabaseEncryptionView(useKeychain: $useKeychain, migration: true) + DatabaseEncryptionView(useKeychain: $useKeychain, migration: true, stopChatRunBlockStartChat: { _, _ in + }) .onChange(of: initialRandomDBPassphrase) { initial in if !initial { migrationState = .uploadConfirmation @@ -318,7 +325,7 @@ struct MigrateFromDevice: View { Text("Cancel migration").foregroundColor(.red) } } - Button(action: { finishMigration(fileId, ctrl) }) { + Button(action: { alert = .finishMigration(fileId, ctrl) }) { settingsRow("checkmark", color: theme.colors.secondary) { Text("Finalize migration").foregroundColor(theme.colors.primary) } @@ -513,19 +520,25 @@ struct MigrateFromDevice: View { chatReceiver = MigrationChatReceiver(ctrl: ctrl, databaseUrl: tempDatabaseUrl) { msg in await MainActor.run { switch msg { - case let .sndFileProgressXFTP(_, _, fileTransferMeta, sentSize, totalSize): + case let .result(.sndFileProgressXFTP(_, _, fileTransferMeta, sentSize, totalSize)): if case let .uploadProgress(uploaded, total, _, _, _) = migrationState, uploaded != total { migrationState = .uploadProgress(uploadedBytes: sentSize, totalBytes: totalSize, fileId: fileTransferMeta.fileId, archivePath: archivePath, ctrl: ctrl) } - case .sndFileRedirectStartXFTP: + case .result(.sndFileRedirectStartXFTP): DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { migrationState = .linkCreation } - case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs): + case let .result(.sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs)): let cfg = getNetCfg() + let proxy: NetworkProxy? = if cfg.socksProxy == nil { + nil + } else { + networkProxyDefault.get() + } let data = MigrationFileLinkData.init( networkConfig: MigrationFileLinkData.NetworkConfig( socksProxy: cfg.socksProxy, + networkProxy: proxy, hostMode: cfg.hostMode, requiredHostMode: cfg.requiredHostMode ) @@ -533,7 +546,7 @@ struct MigrateFromDevice: View { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { migrationState = .linkShown(fileId: fileTransferMeta.fileId, link: data.addToLink(link: rcvURIs[0]), archivePath: archivePath, ctrl: ctrl) } - case .sndFileError: + case .result(.sndFileError): alert = .error(title: "Upload failed", error: "Check your internet connection and try again") migrationState = .uploadFailed(totalBytes: totalBytes, archivePath: archivePath) default: @@ -590,7 +603,7 @@ struct MigrateFromDevice: View { } catch let error { fatalError("Error starting chat \(responseError(error))") } - showSettings = false + dismissAllSheets(animated: true) } } catch let error { alert = .error(title: "Error deleting database", error: responseError(error)) @@ -613,9 +626,7 @@ struct MigrateFromDevice: View { } // Hide settings anyway if chatDbStatus is not ok, probably passphrase needs to be entered if dismiss || m.chatDbStatus != .ok { - await MainActor.run { - showSettings = false - } + dismissAllSheets(animated: true) } } @@ -680,7 +691,7 @@ private struct PassphraseConfirmationView: View { migrationState = .uploadConfirmation } } catch let error { - if case .chatCmdError(_, .errorDatabase(.errorOpen(.errorNotADatabase))) = error as? ChatResponse { + if case .errorDatabase(.errorOpen(.errorNotADatabase)) = error as? ChatError { showErrorOnMigrationIfNeeded(.errorNotADatabase(dbFile: ""), $alert) } else { alert = .error(title: "Error", error: NSLocalizedString("Error verifying passphrase:", comment: "") + " " + String(responseError(error))) @@ -722,11 +733,11 @@ func chatStoppedView() -> some View { private class MigrationChatReceiver { let ctrl: chat_ctrl let databaseUrl: URL - let processReceivedMsg: (ChatResponse) async -> Void + let processReceivedMsg: (APIResult) async -> Void private var receiveLoop: Task? private var receiveMessages = true - init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatResponse) async -> Void) { + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (APIResult) async -> Void) { self.ctrl = ctrl self.databaseUrl = databaseUrl self.processReceivedMsg = processReceivedMsg @@ -741,9 +752,9 @@ private class MigrationChatReceiver { func receiveMsgLoop() async { // TODO use function that has timeout - if let msg = await chatRecvMsg(ctrl) { + if let msg: APIResult = await chatRecvMsg(ctrl) { Task { - await TerminalItems.shared.add(.resp(.now, msg)) + await TerminalItems.shared.addResult(msg) } logger.debug("processReceivedMsg: \(msg.responseType)") await processReceivedMsg(msg) @@ -767,6 +778,6 @@ private class MigrationChatReceiver { struct MigrateFromDevice_Previews: PreviewProvider { static var previews: some View { - MigrateFromDevice(showSettings: Binding.constant(true), showProgressOnSettings: Binding.constant(false)) + MigrateFromDevice(showProgressOnSettings: Binding.constant(false)) } } diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index 67ea1008cd..93fe19cf33 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -93,15 +93,19 @@ struct MigrateToDevice: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction - @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @Binding var migrationState: MigrationToState? @State private var useKeychain = storeDBPassphraseGroupDefault.get() @State private var alert: MigrateToDeviceViewAlert? + @State private var databaseAlert: DatabaseAlert? = nil private let tempDatabaseUrl = urlForTemporaryDatabase() @State private var chatReceiver: MigrationChatReceiver? = nil // Prevent from hiding the view until migration is finished or app deleted @State private var backDisabled: Bool = false @State private var showQRCodeScanner: Bool = true + @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings + + @State private var importingArchiveFromFileProgressIndicator = false + @State private var showFileImporter = false var body: some View { VStack { @@ -175,6 +179,20 @@ struct MigrateToDevice: View { return Alert(title: Text(title), message: Text(error)) } } + .alert(item: $databaseAlert) { item in + switch item { + case .archiveImported: + let (title, message) = archiveImportedAlertText() + return Alert(title: Text(title), message: Text(message)) + case let .archiveImportedWithErrors(errs): + let (title, message) = archiveImportedWithErrorsAlertText(errs: errs) + return Alert(title: Text(title), message: Text(message)) + case let .error(title, error): + return Alert(title: Text(title), message: Text(error)) + default: // not expected this branch to be called because this alert is used only for importArchive purpose + return Alert(title: Text("Error")) + } + } .interactiveDismissDisabled(backDisabled) } @@ -197,11 +215,15 @@ struct MigrateToDevice: View { } } } - if developerTools { - Section(header: Text("Or paste archive link").foregroundColor(theme.colors.secondary)) { - pasteLinkView() - } + Section(header: Text("Or paste archive link").foregroundColor(theme.colors.secondary)) { + pasteLinkView() } + Section(header: Text("Or import archive file").foregroundColor(theme.colors.secondary)) { + archiveImportFromFileView() + } + } + if importingArchiveFromFileProgressIndicator { + progressView() } } } @@ -218,10 +240,38 @@ struct MigrateToDevice: View { } label: { Text("Tap to paste link") } - .disabled(!ChatModel.shared.pasteboardHasStrings) + .disabled(!pasteboardHasStrings) .frame(maxWidth: .infinity, alignment: .center) } + private func archiveImportFromFileView() -> some View { + Button { + showFileImporter = true + } label: { + Label("Import database", systemImage: "square.and.arrow.down") + } + .disabled(importingArchiveFromFileProgressIndicator) + .fileImporter( + isPresented: $showFileImporter, + allowedContentTypes: [.zip], + allowsMultipleSelection: false + ) { result in + if case let .success(files) = result, let fileURL = files.first { + Task { + let success = await DatabaseView.importArchive(fileURL, $importingArchiveFromFileProgressIndicator, $databaseAlert, true) + if success { + DatabaseView.startChat( + Binding.constant(false), + $importingArchiveFromFileProgressIndicator + ) + hideView() + } + } + } + } + } + + private func linkDownloadingView(_ link: String) -> some View { ZStack { List { @@ -446,10 +496,10 @@ struct MigrateToDevice: View { chatReceiver = MigrationChatReceiver(ctrl: ctrl, databaseUrl: tempDatabaseUrl) { msg in await MainActor.run { switch msg { - case let .rcvFileProgressXFTP(_, _, receivedSize, totalSize, rcvFileTransfer): + case let .result(.rcvFileProgressXFTP(_, _, receivedSize, totalSize, rcvFileTransfer)): migrationState = .downloadProgress(downloadedBytes: receivedSize, totalBytes: totalSize, fileId: rcvFileTransfer.fileId, link: link, archivePath: archivePath, ctrl: ctrl) MigrationToDeviceState.save(.downloadProgress(link: link, archiveName: URL(fileURLWithPath: archivePath).lastPathComponent)) - case .rcvStandaloneFileComplete: + case .result(.rcvStandaloneFileComplete): DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // User closed the whole screen before new state was saved if migrationState == nil { @@ -459,10 +509,10 @@ struct MigrateToDevice: View { MigrationToDeviceState.save(.archiveImport(archiveName: URL(fileURLWithPath: archivePath).lastPathComponent)) } } - case .rcvFileError: + case .result(.rcvFileError): alert = .error(title: "Download failed", error: "File was deleted or link is invalid") migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) - case .chatError(_, .error(.noRcvFileUser)): + case .error(.error(.noRcvFileUser)): alert = .error(title: "Download failed", error: "File was deleted or link is invalid") migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) default: @@ -487,6 +537,9 @@ struct MigrateToDevice: View { do { if !hasChatCtrl() { chatInitControllerRemovingDatabases() + } else if ChatModel.shared.chatRunning == true { + // cannot delete storage if chat is running + try await stopChatAsync() } try await apiDeleteStorage() try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) @@ -556,11 +609,22 @@ struct MigrateToDevice: View { do { try? FileManager.default.removeItem(at: getMigrationTempFilesDirectory()) MigrationToDeviceState.save(nil) - appSettings.importIntoApp() - try SimpleX.startChat(refreshInvitations: true) - AlertManager.shared.showAlertMsg(title: "Chat migrated!", message: "Finalize migration on another device.") + try ObjC.catchException { + appSettings.importIntoApp() + } + do { + try SimpleX.startChat(refreshInvitations: true) + AlertManager.shared.showAlertMsg(title: "Chat migrated!", message: "Finalize migration on another device.") + } catch let error { + AlertManager.shared.showAlert(Alert(title: Text("Error starting chat"), message: Text(responseError(error)))) + } } catch let error { - AlertManager.shared.showAlert(Alert(title: Text("Error starting chat"), message: Text(responseError(error)))) + logger.error("Error importing settings: \(error.localizedDescription)") + AlertManager.shared.showAlert( + Alert( + title: Text("Error migrating settings"), + message: Text ("Some app settings were not migrated.") + textNewLine + Text(responseError(error))) + ) } hideView() } @@ -568,6 +632,8 @@ struct MigrateToDevice: View { private func hideView() { onboardingStageDefault.set(.onboardingComplete) m.onboardingStage = .onboardingComplete + m.migrationState = nil + MigrationToDeviceState.save(nil) dismiss() } @@ -685,11 +751,11 @@ private func progressView() -> some View { private class MigrationChatReceiver { let ctrl: chat_ctrl let databaseUrl: URL - let processReceivedMsg: (ChatResponse) async -> Void + let processReceivedMsg: (APIResult) async -> Void private var receiveLoop: Task? private var receiveMessages = true - init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatResponse) async -> Void) { + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (APIResult) async -> Void) { self.ctrl = ctrl self.databaseUrl = databaseUrl self.processReceivedMsg = processReceivedMsg @@ -706,7 +772,7 @@ private class MigrationChatReceiver { // TODO use function that has timeout if let msg = await chatRecvMsg(ctrl) { Task { - await TerminalItems.shared.add(.resp(.now, msg)) + await TerminalItems.shared.addResult(msg) } logger.debug("processReceivedMsg: \(msg.responseType)") await processReceivedMsg(msg) diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift index 6001dff790..3a64a955c5 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -28,7 +28,9 @@ struct AddContactLearnMore: View { 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).") } + .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } .modifier(ThemedBackground(grouped: true)) } diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 33c187b64b..87c0b80372 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -23,7 +23,7 @@ struct AddGroupView: View { @State private var showTakePhoto = false @State private var chosenImage: UIImage? = nil @State private var showInvalidNameAlert = false - @State private var groupLink: String? + @State private var groupLink: CreatedConnLink? @State private var groupLinkMemberRole: GroupMemberRole = .member var body: some View { @@ -137,10 +137,13 @@ struct AddGroupView: View { createInvalidNameAlert(mkValidName(profile.displayName), $profile.displayName) } .onChange(of: chosenImage) { image in - if let image = image { - profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) - } else { - profile.image = nil + Task { + let resized: String? = if let image { + await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + nil + } + await MainActor.run { profile.image = resized } } } .modifier(ThemedBackground(grouped: true)) @@ -188,11 +191,7 @@ struct AddGroupView: View { profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on)) let gInfo = try apiNewGroup(incognito: incognitoDefault, groupProfile: profile) Task { - let groupMembers = await apiListMembers(gInfo.groupId) - await MainActor.run { - m.groupMembers = groupMembers.map { GMember.init($0) } - m.populateGroupMembersIndexes() - } + await m.loadGroupMembers(gInfo) } let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: []) m.addChat(c) diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index bcca763a75..e5263813fa 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -14,9 +14,10 @@ enum ContactType: Int { } struct NewChatMenuButton: View { + // do not use chatModel here because it prevents showing AddGroupMembersView after group creation and QR code after link creation on iOS 16 +// @EnvironmentObject var chatModel: ChatModel @State private var showNewChatSheet = false @State private var alert: SomeAlert? = nil - @State private var globalAlert: SomeAlert? = nil var body: some View { Button { @@ -28,22 +29,10 @@ struct NewChatMenuButton: View { .frame(width: 24, height: 24) } .appSheet(isPresented: $showNewChatSheet) { - NewChatSheet(alert: $alert) + NewChatSheet() .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) - .alert(item: $alert) { a in - return a.alert - } } - // This is a workaround to show "Keep unused invitation" alert in both following cases: - // - on going back from NewChatView to NewChatSheet, - // - on dismissing NewChatMenuButton sheet while on NewChatView (skipping NewChatSheet) - .onChange(of: alert?.id) { a in - if !showNewChatSheet && alert != nil { - globalAlert = alert - alert = nil - } - } - .alert(item: $globalAlert) { a in + .alert(item: $alert) { a in return a.alert } } @@ -60,7 +49,7 @@ struct NewChatSheet: View { @State private var searchText = "" @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil - @Binding var alert: SomeAlert? + @State private var alert: SomeAlert? // Sheet height management @State private var isAddContactActive = false @@ -78,6 +67,9 @@ struct NewChatSheet: View { .navigationBarTitleDisplayMode(.large) .navigationBarHidden(searchMode) .modifier(ThemedBackground(grouped: true)) + .alert(item: $alert) { a in + return a.alert + } } if #available(iOS 16.0, *), oneHandUI { let sheetHeight: CGFloat = showArchive ? 575 : 500 @@ -93,7 +85,7 @@ struct NewChatSheet: View { } } - @ViewBuilder private func viewBody(_ showArchive: Bool) -> some View { + private func viewBody(_ showArchive: Bool) -> some View { List { HStack { ContactsListSearchBar( @@ -112,17 +104,17 @@ struct NewChatSheet: View { if (searchText.isEmpty) { Section { NavigationLink(isActive: $isAddContactActive) { - NewChatView(selection: .invite, parentAlert: $alert) + NewChatView(selection: .invite) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { - navigateOnTap(Label("Add contact", systemImage: "link.badge.plus")) { + navigateOnTap(Label("Create 1-time link", systemImage: "link.badge.plus")) { isAddContactActive = true } } NavigationLink(isActive: $isScanPasteLinkActive) { - NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert) + NewChatView(selection: .connect, showQRCodeScanner: true) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) @@ -194,12 +186,12 @@ struct NewChatSheet: View { } } -func chatContactType(chat: Chat) -> ContactType { +func chatContactType(_ chat: Chat) -> ContactType { switch chat.chatInfo { case .contactRequest: return .request case let .direct(contact): - if contact.activeConn == nil && contact.profile.contactLink != nil { + if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { return .card } else if contact.chatDeleted { return .chatDeleted @@ -215,7 +207,7 @@ func chatContactType(chat: Chat) -> ContactType { private func filterContactTypes(chats: [Chat], contactTypes: [ContactType]) -> [Chat] { return chats.filter { chat in - contactTypes.contains(chatContactType(chat: chat)) + contactTypes.contains(chatContactType(chat)) } } @@ -266,7 +258,7 @@ struct ContactsList: View { } } - @ViewBuilder private func noResultSection(text: String) -> some View { + private func noResultSection(text: String) -> some View { Section { Text(text) .foregroundColor(theme.colors.secondary) @@ -287,8 +279,8 @@ struct ContactsList: View { } private func chatsByTypeComparator(chat1: Chat, chat2: Chat) -> Bool { - let chat1Type = chatContactType(chat: chat1) - let chat2Type = chatContactType(chat: chat2) + let chat1Type = chatContactType(chat1) + let chat2Type = chatContactType(chat2) if chat1Type.rawValue < chat2Type.rawValue { return true diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 6cbc65e7c9..110eda7882 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -25,6 +25,7 @@ struct SomeActionSheet: Identifiable { struct SomeSheet: Identifiable { @ViewBuilder var content: Content var id: String + var fraction = 0.4 } private enum NewChatViewAlert: Identifiable { @@ -45,29 +46,64 @@ enum NewChatOption: Identifiable { var id: Self { self } } +func showKeepInvitationAlert() { + if let showingInvitation = ChatModel.shared.showingInvitation, + !showingInvitation.connChatUsed { + showAlert( + NSLocalizedString("Keep unused invitation?", comment: "alert title"), + message: NSLocalizedString("You can view invitation link again in connection details.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Keep", comment: "alert action"), + style: .default + ), + UIAlertAction( + title: NSLocalizedString("Delete", comment: "alert action"), + style: .destructive, + handler: { _ in + Task { + await deleteChat(Chat( + chatInfo: .contactConnection(contactConnection: showingInvitation.pcc), + chatItems: [] + )) + } + } + ) + ]} + ) + } + ChatModel.shared.showingInvitation = nil +} + struct NewChatView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @State var selection: NewChatOption @State var showQRCodeScanner = false @State private var invitationUsed: Bool = false - @State private var contactConnection: PendingContactConnection? = nil - @State private var connReqInvitation: String = "" + @State private var connLinkInvitation: CreatedConnLink = CreatedConnLink(connFullLink: "", connShortLink: nil) + @State private var showShortLink = true @State private var creatingConnReq = false + @State var choosingProfile = false @State private var pastedLink: String = "" @State private var alert: NewChatViewAlert? - @Binding var parentAlert: SomeAlert? + @State private var contactConnection: PendingContactConnection? = nil var body: some View { VStack(alignment: .leading) { Picker("New chat", selection: $selection) { - Label("Add contact", systemImage: "link") + Label("1-time link", systemImage: "link") .tag(NewChatOption.invite) Label("Connect via link", systemImage: "qrcode") .tag(NewChatOption.connect) } .pickerStyle(.segmented) .padding() + .onChange(of: $selection.wrappedValue) { opt in + if opt == NewChatOption.connect { + showQRCodeScanner = true + } + } VStack { // it seems there's a bug in iOS 15 if several views in switch (or if-else) statement have different transitions @@ -122,26 +158,10 @@ struct NewChatView: View { } } .onDisappear { - if !(m.showingInvitation?.connChatUsed ?? true), - let conn = contactConnection { - parentAlert = SomeAlert( - alert: Alert( - title: Text("Keep unused invitation?"), - message: Text("You can view invitation link again in connection details."), - primaryButton: .default(Text("Keep")) {}, - secondaryButton: .destructive(Text("Delete")) { - Task { - await deleteChat(Chat( - chatInfo: .contactConnection(contactConnection: conn), - chatItems: [] - )) - } - } - ), - id: "keepUnusedInvitation" - ) + if !choosingProfile { + showKeepInvitationAlert() + contactConnection = nil } - m.showingInvitation = nil } .alert(item: $alert) { a in switch(a) { @@ -155,11 +175,13 @@ struct NewChatView: View { private func prepareAndInviteView() -> some View { ZStack { // ZStack is needed for views to not make transitions between each other - if connReqInvitation != "" { + if connLinkInvitation.connFullLink != "" { InviteView( invitationUsed: $invitationUsed, contactConnection: $contactConnection, - connReqInvitation: connReqInvitation + connLinkInvitation: $connLinkInvitation, + showShortLink: $showShortLink, + choosingProfile: $choosingProfile ) } else if creatingConnReq { creatingLinkProgressView() @@ -170,16 +192,16 @@ struct NewChatView: View { } private func createInvitation() { - if connReqInvitation == "" && contactConnection == nil && !creatingConnReq { + if connLinkInvitation.connFullLink == "" && contactConnection == nil && !creatingConnReq { creatingConnReq = true Task { _ = try? await Task.sleep(nanoseconds: 250_000000) let (r, apiAlert) = await apiAddContact(incognito: incognitoGroupDefault.get()) - if let (connReq, pcc) = r { + if let (connLink, pcc) = r { await MainActor.run { m.updateContactConnection(pcc) - m.showingInvitation = ShowingInvitation(connId: pcc.id, connChatUsed: false) - connReqInvitation = connReq + m.showingInvitation = ShowingInvitation(pcc: pcc, connChatUsed: false) + connLinkInvitation = connLink contactConnection = pcc } } else { @@ -210,12 +232,23 @@ struct NewChatView: View { } } +private func incognitoProfileImage() -> some View { + Image(systemName: "theatermasks.fill") + .resizable() + .scaledToFit() + .frame(width: 30) + .foregroundColor(.indigo) +} + private struct InviteView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var invitationUsed: Bool @Binding var contactConnection: PendingContactConnection? - var connReqInvitation: String + @Binding var connLinkInvitation: CreatedConnLink + @Binding var showShortLink: Bool + @Binding var choosingProfile: Bool + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false var body: some View { @@ -226,35 +259,47 @@ private struct InviteView: View { .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10)) qrCodeView() - - Section { - IncognitoToggle(incognitoEnabled: $incognitoDefault) - } footer: { - sharedProfileInfo(incognitoDefault) - .foregroundColor(theme.colors.secondary) + if let selectedProfile = chatModel.currentUser { + Section { + NavigationLink { + ActiveProfilePicker( + contactConnection: $contactConnection, + connLinkInvitation: $connLinkInvitation, + incognitoEnabled: $incognitoDefault, + choosingProfile: $choosingProfile, + selectedProfile: selectedProfile + ) + } label: { + HStack { + if incognitoDefault { + incognitoProfileImage() + Text("Incognito") + } else { + ProfileImage(imageStr: chatModel.currentUser?.image, size: 30) + Text(chatModel.currentUser?.chatViewName ?? "") + } + } + } + } header: { + Text("Share profile").foregroundColor(theme.colors.secondary) + } footer: { + if incognitoDefault { + Text("A new random profile will be shared.") + } + } } } .onChange(of: incognitoDefault) { incognito in - Task { - do { - if let contactConn = contactConnection, - let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { - await MainActor.run { - contactConnection = conn - chatModel.updateContactConnection(conn) - } - } - } catch { - logger.error("apiSetConnectionIncognito error: \(responseError(error))") - } - } + setInvitationUsed() + } + .onChange(of: chatModel.currentUser) { u in setInvitationUsed() } } private func shareLinkView() -> some View { HStack { - let link = simplexChatLink(connReqInvitation) + let link = connLinkInvitation.simplexChatUri(short: showShortLink) linkTextView(link) Button { showShareSheet(items: [link]) @@ -268,8 +313,9 @@ private struct InviteView: View { } private func qrCodeView() -> some View { - Section(header: Text("Or show this code").foregroundColor(theme.colors.secondary)) { - SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed) + Section { + SimpleXCreatedLinkQRCode(link: connLinkInvitation, short: $showShortLink, onShare: setInvitationUsed) + .id("simplex-qrcode-view-for-\(connLinkInvitation.simplexChatUri(short: showShortLink))") .padding() .background( RoundedRectangle(cornerRadius: 12, style: .continuous) @@ -279,6 +325,8 @@ private struct InviteView: View { .listRowBackground(Color.clear) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } header: { + ToggleShortLinkHeader(text: Text("Or show this code"), link: connLinkInvitation, short: $showShortLink) } } @@ -289,6 +337,256 @@ private struct InviteView: View { } } +private enum ProfileSwitchStatus { + case switchingUser + case switchingIncognito + case idle +} + +private struct ActiveProfilePicker: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @Binding var contactConnection: PendingContactConnection? + @Binding var connLinkInvitation: CreatedConnLink + @Binding var incognitoEnabled: Bool + @Binding var choosingProfile: Bool + @State private var alert: SomeAlert? + @State private var profileSwitchStatus: ProfileSwitchStatus = .idle + @State private var switchingProfileByTimeout = false + @State private var lastSwitchingProfileByTimeoutCall: Double? + @State private var profiles: [User] = [] + @State private var searchTextOrPassword = "" + @State private var showIncognitoSheet = false + @State private var incognitoFirst: Bool = false + @State var selectedProfile: User + var trimmedSearchTextOrPassword: String { searchTextOrPassword.trimmingCharacters(in: .whitespaces)} + + var body: some View { + viewBody() + .navigationTitle("Select chat profile") + .searchable(text: $searchTextOrPassword, placement: .navigationBarDrawer(displayMode: .always)) + .autocorrectionDisabled(true) + .navigationBarTitleDisplayMode(.large) + .onAppear { + profiles = chatModel.users + .map { $0.user } + .sorted { u, _ in u.activeUser } + } + .onChange(of: incognitoEnabled) { incognito in + if profileSwitchStatus != .switchingIncognito { + return + } + + Task { + do { + if let contactConn = contactConnection, + let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { + await MainActor.run { + contactConnection = conn + chatModel.updateContactConnection(conn) + profileSwitchStatus = .idle + dismiss() + } + } + } catch { + profileSwitchStatus = .idle + incognitoEnabled = !incognito + logger.error("apiSetConnectionIncognito error: \(responseError(error))") + let err = getErrorAlert(error, "Error changing to incognito!") + + alert = SomeAlert( + alert: Alert( + title: Text(err.title), + message: Text(err.message ?? "Error: \(responseError(error))") + ), + id: "setConnectionIncognitoError" + ) + } + } + } + .onChange(of: profileSwitchStatus) { sp in + if sp != .idle { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + switchingProfileByTimeout = profileSwitchStatus != .idle + } + } else { + switchingProfileByTimeout = false + } + } + .onChange(of: selectedProfile) { profile in + if (profileSwitchStatus != .switchingUser) { + return + } + Task { + do { + if let contactConn = contactConnection { + let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) + await MainActor.run { + contactConnection = conn + connLinkInvitation = conn.connLinkInv ?? CreatedConnLink(connFullLink: "", connShortLink: nil) + incognitoEnabled = false + chatModel.updateContactConnection(conn) + } + do { + try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil ) + await MainActor.run { + profileSwitchStatus = .idle + dismiss() + } + } catch { + await MainActor.run { + profileSwitchStatus = .idle + alert = SomeAlert( + alert: Alert( + title: Text("Error switching profile"), + message: Text("Your connection was moved to \(profile.chatViewName) but an unexpected error occurred while redirecting you to the profile.") + ), + id: "switchingProfileError" + ) + } + } + } + } catch { + await MainActor.run { + profileSwitchStatus = .idle + if let currentUser = chatModel.currentUser { + selectedProfile = currentUser + } + let err = getErrorAlert(error, "Error changing connection profile") + alert = SomeAlert( + alert: Alert( + title: Text(err.title), + message: Text(err.message ?? "Error: \(responseError(error))") + ), + id: "changeConnectionUserError" + ) + } + } + } + } + .alert(item: $alert) { a in + a.alert + } + .onAppear { + incognitoFirst = incognitoEnabled + choosingProfile = true + } + .onDisappear { + choosingProfile = false + } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } + } + + + @ViewBuilder private func viewBody() -> some View { + profilePicker() + .allowsHitTesting(!switchingProfileByTimeout) + .modifier(ThemedBackground(grouped: true)) + .overlay { + if switchingProfileByTimeout { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + } + + private func filteredProfiles() -> [User] { + let s = trimmedSearchTextOrPassword + let lower = s.localizedLowercase + + return profiles.filter { u in + if (u.activeUser || !u.hidden) && (s == "" || u.chatViewName.localizedLowercase.contains(lower)) { + return true + } + return correctPassword(u, s) + } + } + + private func profilerPickerUserOption(_ user: User) -> some View { + Button { + if selectedProfile == user && incognitoEnabled { + incognitoEnabled = false + profileSwitchStatus = .switchingIncognito + } else if selectedProfile != user { + selectedProfile = user + profileSwitchStatus = .switchingUser + } + } label: { + HStack { + ProfileImage(imageStr: user.image, size: 30) + .padding(.trailing, 2) + Text(user.chatViewName) + .foregroundColor(theme.colors.onBackground) + .lineLimit(1) + Spacer() + if selectedProfile == user, !incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } + } + } + } + + @ViewBuilder private func profilePicker() -> some View { + let incognitoOption = Button { + if !incognitoEnabled { + incognitoEnabled = true + profileSwitchStatus = .switchingIncognito + } + } label : { + HStack { + incognitoProfileImage() + Text("Incognito") + .foregroundColor(theme.colors.onBackground) + Image(systemName: "info.circle") + .foregroundColor(theme.colors.primary) + .font(.system(size: 14)) + .onTapGesture { + showIncognitoSheet = true + } + Spacer() + if incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } + } + } + + List { + let filteredProfiles = filteredProfiles() + let activeProfile = filteredProfiles.first { u in u.activeUser } + + if let selectedProfile = activeProfile { + let otherProfiles = filteredProfiles.filter { u in u.userId != activeProfile?.userId } + + if incognitoFirst { + incognitoOption + profilerPickerUserOption(selectedProfile) + } else { + profilerPickerUserOption(selectedProfile) + incognitoOption + } + + ForEach(otherProfiles) { p in + profilerPickerUserOption(p) + } + } else { + incognitoOption + ForEach(filteredProfiles) { p in + profilerPickerUserOption(p) + } + } + } + .opacity(switchingProfileByTimeout ? 0.4 : 1) + } +} + private struct ConnectView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @@ -296,6 +594,7 @@ private struct ConnectView: View { @Binding var pastedLink: String @Binding var alert: NewChatViewAlert? @State private var sheet: PlanAndConnectActionSheet? + @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings var body: some View { List { @@ -332,7 +631,7 @@ private struct ConnectView: View { } label: { Text("Tap to paste link") } - .disabled(!ChatModel.shared.pasteboardHasStrings) + .disabled(!pasteboardHasStrings) .frame(maxWidth: .infinity, alignment: .center) } else { linkTextView(pastedLink) @@ -403,6 +702,7 @@ struct ScannerInView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .foregroundColor(Color.clear) switch cameraAuthorizationStatus { + case .authorized, nil: EmptyView() case .restricted: Text("Camera not available") case .denied: Label("Enable camera access", systemImage: "camera") default: Label("Tap to scan", systemImage: "qrcode") @@ -422,21 +722,26 @@ struct ScannerInView: View { .disabled(cameraAuthorizationStatus == .restricted) } } - .onAppear { + .task { let status = AVCaptureDevice.authorizationStatus(for: .video) cameraAuthorizationStatus = status if showQRCodeScanner { switch status { - case .notDetermined: askCameraAuthorization() + case .notDetermined: await askCameraAuthorizationAsync() case .restricted: showQRCodeScanner = false case .denied: showQRCodeScanner = false case .authorized: () - @unknown default: askCameraAuthorization() + @unknown default: await askCameraAuthorizationAsync() } } } } + func askCameraAuthorizationAsync() async { + await AVCaptureDevice.requestAccess(for: .video) + cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + } + func askCameraAuthorization(_ cb: (() -> Void)? = nil) { AVCaptureDevice.requestAccess(for: .video) { allowed in cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) @@ -535,23 +840,25 @@ func sharedProfileInfo(_ incognito: Bool) -> Text { } enum PlanAndConnectAlert: Identifiable { - case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case invitationLinkConnecting(connectionLink: String) - case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?) + case ownInvitationLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case invitationLinkConnecting(connectionLink: CreatedConnLink) + case ownContactAddressConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case contactAddressConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnecting(connectionLink: CreatedConnLink, groupInfo: GroupInfo?) + case error(shortOrFullLink: String, alert: Alert) var id: String { switch self { - case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)" - case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)" - case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)" - case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)" - case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)" - case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)" - case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)" + case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink.connFullLink)" + case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink.connFullLink)" + case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink.connFullLink)" + case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink.connFullLink)" + case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink.connFullLink)" + case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink.connFullLink)" + case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink.connFullLink)" + case let .error(shortOrFullLink, alert): return "error \(shortOrFullLink)" } } } @@ -616,11 +923,17 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: ( ) case let .groupLinkConnecting(_, groupInfo): if let groupInfo = groupInfo { - return Alert( + return groupInfo.businessChat == nil + ? Alert( title: Text("Group already exists!"), message: Text("You are already joining the group \(groupInfo.displayName)."), dismissButton: .default(Text("OK")) { cleanup?() } ) + : Alert( + title: Text("Chat already exists!"), + message: Text("You are already connecting to \(groupInfo.displayName)."), + dismissButton: .default(Text("OK")) { cleanup?() } + ) } else { return Alert( title: Text("Already joining the group!"), @@ -628,21 +941,22 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: ( dismissButton: .default(Text("OK")) { cleanup?() } ) } + case let .error(_, alert): return alert } } enum PlanAndConnectActionSheet: Identifiable { - case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) - case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey) + case askCurrentOrIncognitoProfile(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) + case askCurrentOrIncognitoProfileDestructive(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, title: LocalizedStringKey) case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact) - case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) + case ownGroupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) var id: String { switch self { - case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)" - case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)" + case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink.connFullLink)" + case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink.connFullLink)" case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)" - case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)" + case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink.connFullLink)" } } } @@ -701,7 +1015,7 @@ func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool } func planAndConnect( - _ connectionLink: String, + _ shortOrFullLink: String, showAlert: @escaping (PlanAndConnectAlert) -> Void, showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void, dismiss: Bool, @@ -711,8 +1025,8 @@ func planAndConnect( filterKnownGroup: ((GroupInfo) -> Void)? = nil ) { Task { - do { - let connectionPlan = try await apiConnectPlan(connReq: connectionLink) + let (result, alert) = await apiConnectPlan(connLink: shortOrFullLink) + if let (connectionLink, connectionPlan) = result { switch connectionPlan { case let .invitationLink(ilp): switch ilp { @@ -721,32 +1035,40 @@ func planAndConnect( if let incognito = incognito { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) + await MainActor.run { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) + } } case .ownLink: logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + await MainActor.run { + if let incognito = incognito { + showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + } } case let .connecting(contact_): logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")") - if let contact = contact_ { - if let f = filterKnownContact { - f(contact) + await MainActor.run { + if let contact = contact_ { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + } } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) } - } else { - showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) } case let .known(contact): logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + } } } case let .contactAddress(cap): @@ -756,83 +1078,109 @@ func planAndConnect( if let incognito = incognito { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) + await MainActor.run { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) + } } case .ownLink: logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) + await MainActor.run { + if let incognito = incognito { + showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) + } } case .connectingConfirmReconnect: logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) + await MainActor.run { + if let incognito = incognito { + showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) + } } case let .connectingProhibit(contact): logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + } } case let .known(contact): logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + } } case let .contactViaAddress(contact): logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")") if let incognito = incognito { connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } else { - showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) + await MainActor.run { + showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) + } } } case let .groupLink(glp): switch glp { case .ok: - if let incognito = incognito { - showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) + await MainActor.run { + if let incognito = incognito { + showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) + } } case let .ownLink(groupInfo): logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownGroup { - f(groupInfo) + await MainActor.run { + if let f = filterKnownGroup { + f(groupInfo) + } + showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) } - showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) case .connectingConfirmReconnect: logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) + await MainActor.run { + if let incognito = incognito { + showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) + } } case let .connectingProhibit(groupInfo_): logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) + await MainActor.run { + showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) + } case let .known(groupInfo): logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownGroup { - f(groupInfo) - } else { - openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } + await MainActor.run { + if let f = filterKnownGroup { + f(groupInfo) + } else { + openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } + } } } + case let .error(chatError): + logger.debug("planAndConnect, .error \(chatErrorString(chatError))") + if let incognito = incognito { + connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) + } } - } catch { - logger.debug("planAndConnect, plan error") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) + } else if let alert { + await MainActor.run { + showAlert(.error(shortOrFullLink: shortOrFullLink, alert: alert)) } } } @@ -854,22 +1202,22 @@ private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incogn } private func connectViaLink( - _ connectionLink: String, + _ connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, dismiss: Bool, incognito: Bool, cleanup: (() -> Void)? ) { Task { - if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) { + if let (connReqType, pcc) = await apiConnect(incognito: incognito, connLink: connectionLink) { await MainActor.run { ChatModel.shared.updateContactConnection(pcc) } let crt: ConnReqType - if let plan = connectionPlan { - crt = planToConnReqType(plan) + crt = if let plan = connectionPlan { + planToConnReqType(plan) ?? connReqType } else { - crt = connReqType + connReqType } DispatchQueue.main.async { if dismiss { @@ -892,39 +1240,35 @@ private func connectViaLink( } func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - Task { - let m = ChatModel.shared - if let c = m.getContactChat(contact.contactId) { - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(c.id) - showAlreadyExistsAlert?() - } - } else { - ItemsModel.shared.loadOpenChat(c.id) + let m = ChatModel.shared + if let c = m.getContactChat(contact.contactId) { + if dismiss { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(c.id) { showAlreadyExistsAlert?() } } + } else { + ItemsModel.shared.loadOpenChat(c.id) { + showAlreadyExistsAlert?() + } } } } func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - Task { - let m = ChatModel.shared - if let g = m.getGroupChat(groupInfo.groupId) { - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(g.id) - showAlreadyExistsAlert?() - } - } else { - ItemsModel.shared.loadOpenChat(g.id) + let m = ChatModel.shared + if let g = m.getGroupChat(groupInfo.groupId) { + if dismiss { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(g.id) { showAlreadyExistsAlert?() } } + } else { + ItemsModel.shared.loadOpenChat(g.id) { + showAlreadyExistsAlert?() + } } } } @@ -937,10 +1281,15 @@ func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert { } func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert { - mkAlert( + groupInfo.businessChat == nil + ? mkAlert( title: "Group already exists", message: "You are already in group \(groupInfo.displayName)." ) + : mkAlert( + title: "Chat already exists", + message: "You are already connected with \(groupInfo.displayName)." + ) } enum ConnReqType: Equatable { @@ -957,11 +1306,12 @@ enum ConnReqType: Equatable { } } -private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType { +private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType? { switch connectionPlan { - case .invitationLink: return .invitation - case .contactAddress: return .contact - case .groupLink: return .groupLink + case .invitationLink: .invitation + case .contactAddress: .contact + case .groupLink: .groupLink + case .error: nil } } @@ -975,10 +1325,10 @@ func connReqSentAlert(_ type: ConnReqType) -> Alert { struct NewChatView_Previews: PreviewProvider { static var previews: some View { @State var parentAlert: SomeAlert? + @State var contactConnection: PendingContactConnection? = nil NewChatView( - selection: .invite, - parentAlert: $parentAlert + selection: .invite ) } } diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index e3bae9287a..453149198b 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -8,6 +8,7 @@ import SwiftUI import CoreImage.CIFilterBuiltins +import SimpleXChat struct MutableQRCode: View { @Binding var uri: String @@ -20,6 +21,16 @@ struct MutableQRCode: View { } } +struct SimpleXCreatedLinkQRCode: View { + let link: CreatedConnLink + @Binding var short: Bool + var onShare: (() -> Void)? = nil + + var body: some View { + QRCode(uri: link.simplexChatUri(short: short), onShare: onShare) + } +} + struct SimpleXLinkQRCode: View { let uri: String var withLogo: Bool = true @@ -31,12 +42,6 @@ struct SimpleXLinkQRCode: View { } } -func simplexChatLink(_ uri: String) -> String { - uri.starts(with: "simplex:/") - ? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") - : uri -} - struct QRCode: View { let uri: String var withLogo: Bool = true @@ -49,34 +54,34 @@ struct QRCode: View { ZStack { if let image = image { qrCodeImage(image) - } - GeometryReader { geo in - ZStack { - if withLogo { - let w = geo.size.width - Image("icon-light") - .resizable() - .scaledToFit() - .frame(width: w * 0.16, height: w * 0.16) - .frame(width: w * 0.165, height: w * 0.165) - .background(.white) - .clipShape(Circle()) + GeometryReader { geo in + ZStack { + if withLogo { + let w = geo.size.width + Image("icon-light") + .resizable() + .scaledToFit() + .frame(width: w * 0.16, height: w * 0.16) + .frame(width: w * 0.165, height: w * 0.165) + .background(.white) + .clipShape(Circle()) + } } - } - .onAppear { - makeScreenshotFunc = { - let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale) - showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)]) - onShare?() + .onAppear { + makeScreenshotFunc = { + let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale) + showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)]) + onShare?() + } } + .frame(width: geo.size.width, height: geo.size.height) } - .frame(width: geo.size.width, height: geo.size.height) + } else { + Color.clear.aspectRatio(1, contentMode: .fit) } } .onTapGesture(perform: makeScreenshotFunc) - .onAppear { - image = image ?? generateImage(uri, tintColor: tintColor) - } + .task { image = await generateImage(uri, tintColor: tintColor) } .frame(maxWidth: .infinity, maxHeight: .infinity) } } @@ -89,7 +94,7 @@ private func qrCodeImage(_ image: UIImage) -> some View { .textSelection(.enabled) } -private func generateImage(_ uri: String, tintColor: UIColor) -> UIImage? { +private func generateImage(_ uri: String, tintColor: UIColor) async -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(uri.utf8) diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift new file mode 100644 index 0000000000..c8d0faafa7 --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift @@ -0,0 +1,109 @@ +// +// AddressCreationCard.swift +// SimpleX (iOS) +// +// Created by Diogo Cunha on 13/11/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct AddressCreationCard: View { + @EnvironmentObject var theme: AppTheme + @EnvironmentObject private var chatModel: ChatModel + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + @AppStorage(DEFAULT_ADDRESS_CREATION_CARD_SHOWN) private var addressCreationCardShown = false + @State private var showAddressCreationAlert = false + @State private var showAddressSheet = false + @State private var showAddressInfoSheet = false + + var body: some View { + let addressExists = chatModel.userAddress != nil + let chats = chatModel.chats.filter { chat in + !chat.chatInfo.chatDeleted && !chat.chatInfo.contactCard + } + ZStack(alignment: .topTrailing) { + HStack(alignment: .top, spacing: 16) { + let envelopeSize = dynamicSize(userFont).profileImageSize + Image(systemName: "envelope.circle.fill") + .resizable() + .frame(width: envelopeSize, height: envelopeSize) + .foregroundColor(.accentColor) + VStack(alignment: .leading) { + Text("Your SimpleX address") + .font(.title3) + Spacer() + Text("How to use it") + textSpace + Text(Image(systemName: "info.circle")).foregroundColor(theme.colors.secondary) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .trailing) { + Image(systemName: "multiply") + .foregroundColor(theme.colors.secondary) + .onTapGesture { + showAddressCreationAlert = true + } + Spacer() + Text("Create") + .foregroundColor(.accentColor) + .onTapGesture { + showAddressSheet = true + } + } + } + .onTapGesture { + showAddressInfoSheet = true + } + .padding() + .background(theme.appColors.sentMessage) + .cornerRadius(12) + .frame(height: dynamicSize(userFont).rowHeight) + .alert(isPresented: $showAddressCreationAlert) { + Alert( + title: Text("SimpleX address"), + message: Text("Tap Create SimpleX address in the menu to create it later."), + dismissButton: .default(Text("Ok")) { + withAnimation { + addressCreationCardShown = true + } + } + ) + } + .sheet(isPresented: $showAddressSheet) { + NavigationView { + UserAddressView(autoCreate: true) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + } + .sheet(isPresented: $showAddressInfoSheet) { + NavigationView { + UserAddressLearnMore(showCreateAddressButton: true) + .navigationTitle("Address or 1-time link?") + .navigationBarTitleDisplayMode(.inline) + .modifier(ThemedBackground(grouped: true)) + } + } + .onChange(of: addressExists) { exists in + if exists, !addressCreationCardShown { + addressCreationCardShown = true + } + } + .onChange(of: chats.count) { size in + if size >= 3, !addressCreationCardShown { + addressCreationCardShown = true + } + } + .onAppear { + if addressExists, !addressCreationCardShown { + addressCreationCardShown = true + } + } + } +} + +#Preview { + AddressCreationCard() +} diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift new file mode 100644 index 0000000000..656cef4a04 --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -0,0 +1,412 @@ +// +// ChooseServerOperators.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 31.10.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +let conditionsURL = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md")! + +struct OnboardingButtonStyle: ButtonStyle { + @EnvironmentObject var theme: AppTheme + var isDisabled: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.system(size: 17, weight: .semibold)) + .padding() + .frame(maxWidth: .infinity) + .background( + isDisabled + ? ( + theme.colors.isLight + ? .gray.opacity(0.17) + : .gray.opacity(0.27) + ) + : theme.colors.primary + ) + .foregroundColor( + isDisabled + ? ( + theme.colors.isLight + ? .gray.opacity(0.4) + : .white.opacity(0.2) + ) + : .white + ) + .cornerRadius(16) + .scaleEffect(configuration.isPressed ? 0.95 : 1.0) + } +} + +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 + + var selectedOperators: [ServerOperator] { serverOperators.filter { selectedOperatorIds.contains($0.operatorId) } } + + 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) + + Spacer() + + 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) + + Button("Privacy policy and conditions of use.") { + sheetItem = .showConditions + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(.horizontal, 4) + + Spacer() + + VStack(spacing: 12) { + acceptConditionsButton() + + Button("Configure server operators") { + sheetItem = .configureOperators + } + .frame(minHeight: 40) + } + } + .padding(25) + .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 .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 + } + } + .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 { + ZStack { + button() + + NavigationLink(isActive: $notificationsModeNavLinkActive) { + notificationsModeDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + 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 acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + let operatorIds = acceptForOperators.map { $0.operatorId } + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + } + 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 { + 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) + } + } + + private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { + var ops = operators + if !ops.isEmpty { + for i in 0.. + @State private var sheetItem: ChooseServerOperatorsSheet? = nil + + var body: some View { + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Text("Server operators") + .font(.largeTitle) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .padding(.top, 25) + + infoText() + .frame(maxWidth: .infinity, alignment: .center) + + Spacer() + + ForEach(serverOperators) { srvOperator in + operatorCheckView(srvOperator) + } + VStack { + Text("SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app.").padding(.bottom, 8) + Text("You can configure servers via settings.") + } + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 16) + + Spacer() + + VStack(spacing: 8) { + setOperatorsButton() + onboardingButtonPlaceholder() + } + } + .frame(minHeight: g.size.height) + } + .sheet(item: $sheetItem) { item in + switch item { + case .showInfo: + ChooseServerOperatorsInfoView() + } + } + .frame(maxHeight: .infinity, alignment: .top) + } + .frame(maxHeight: .infinity, alignment: .top) + .padding(25) + .interactiveDismissDisabled(selectedOperatorIds.isEmpty) + } + + private func infoText() -> some View { + Button { + sheetItem = .showInfo + } label: { + Label("How it helps privacy", systemImage: "info.circle") + .font(.headline) + } + } + + private func operatorCheckView(_ serverOperator: ServerOperator) -> some View { + let checked = selectedOperatorIds.contains(serverOperator.operatorId) + let icon = checked ? "checkmark.circle.fill" : "circle" + let iconColor = checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel).asAnotherColorFromSecondary(theme) + return HStack(spacing: 10) { + Image(serverOperator.largeLogo(colorScheme)) + .resizable() + .scaledToFit() + .frame(height: 48) + Spacer() + Image(systemName: icon) + .resizable() + .scaledToFit() + .frame(width: 26, height: 26) + .foregroundColor(iconColor) + } + .background(theme.colors.background) + .padding() + .clipShape(RoundedRectangle(cornerRadius: 18)) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color(uiColor: .secondarySystemFill), lineWidth: 2) + ) + .padding(.horizontal, 2) + .onTapGesture { + if checked { + selectedOperatorIds.remove(serverOperator.operatorId) + } else { + selectedOperatorIds.insert(serverOperator.operatorId) + } + } + } + + private func setOperatorsButton() -> some View { + Button { + dismiss() + } label: { + Text("OK") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } +} + +let operatorsPostLink = URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html")! + +struct ChooseServerOperatorsInfoView: View { + @Environment(\.colorScheme) var colorScheme: ColorScheme + @EnvironmentObject var theme: AppTheme + + var body: some View { + NavigationView { + List { + VStack(alignment: .leading, spacing: 12) { + Text("The app protects your privacy by using different operators in each conversation.") + Text("When more than one operator is enabled, none of them has metadata to learn who communicates with whom.") + Text("For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server.") + } + .fixedSize(horizontal: false, vertical: true) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.top) + + Section { + ForEach(ChatModel.shared.conditions.serverOperators) { op in + operatorInfoNavLinkView(op) + } + } header: { + Text("About operators") + .foregroundColor(theme.colors.secondary) + } + } + .navigationTitle("Server operators") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + } + + private func operatorInfoNavLinkView(_ op: ServerOperator) -> some View { + NavigationLink() { + OperatorInfoView(serverOperator: op) + .navigationBarTitle("Network operator") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + HStack { + Image(op.logo(colorScheme)) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + Text(op.tradeName) + } + } + } +} + +#Preview { + OnboardingConditionsView() +} diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 487f4ccdeb..ae72cb1be5 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -38,7 +38,7 @@ struct CreateProfile: View { TextField("Enter your name…", text: $displayName) .focused($focusDisplayName) Button { - createProfile(displayName, showAlert: { alert = $0 }, dismiss: dismiss) + createProfile() } label: { Label("Create profile", systemImage: "checkmark") } @@ -62,8 +62,7 @@ struct CreateProfile: View { .frame(height: 20) } footer: { VStack(alignment: .leading, spacing: 8) { - Text("Your profile, contacts and delivered messages are stored on your device.") - Text("The profile is only shared with your contacts.") + Text("Your profile is stored on your device and only shared with your contacts.") } .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity, alignment: .leading) @@ -78,6 +77,35 @@ struct CreateProfile: View { } } } + + private func createProfile() { + hideKeyboard() + let profile = Profile( + displayName: displayName.trimmingCharacters(in: .whitespaces), + fullName: "" + ) + let m = ChatModel.shared + do { + AppChatState.shared.set(.active) + m.currentUser = try apiCreateActiveUser(profile) + // .isEmpty check is redundant here, but it makes it clearer what is going on + if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { + try startChat() + withAnimation { + onboardingStageDefault.set(.step3_ChooseServerOperators) + m.onboardingStage = .step3_ChooseServerOperators + } + } else { + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete + dismiss() + m.users = try listUsers() + try getUserChatData() + } + } catch let error { + showCreateProfileAlert(showAlert: { alert = $0 }, error) + } + } } struct CreateFirstProfile: View { @@ -86,131 +114,158 @@ struct CreateFirstProfile: View { @Environment(\.dismiss) var dismiss @State private var displayName: String = "" @FocusState private var focusDisplayName + @State private var nextStepNavLinkActive = false var body: some View { - VStack(alignment: .leading) { - Group { - Text("Create your profile") + let v = VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .center, spacing: 16) { + Text("Create profile") .font(.largeTitle) .bold() - Text("Your profile, contacts and delivered messages are stored on your device.") - .foregroundColor(theme.colors.secondary) - Text("The profile is only shared with your contacts.") - .foregroundColor(theme.colors.secondary) - .padding(.bottom) - } - .padding(.bottom) + .multilineTextAlignment(.center) - ZStack(alignment: .topLeading) { + 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) - if name != validName { - Button { - showAlert(.invalidNameError(validName: validName)) - } label: { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) + 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)) + ) + if name != validName { + Button { + showAlert(.invalidNameError(validName: validName)) + } label: { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .padding(.horizontal, 10) + } } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) } - TextField("Enter your name…", text: $displayName) - .focused($focusDisplayName) - .padding(.leading, 32) } - .padding(.bottom) + .padding(.top) + Spacer() - onboardingButtons() + + VStack(spacing: 10) { + createProfileButton() + if !focusDisplayName { + onboardingButtonPlaceholder() + } + } } .onAppear() { - focusDisplayName = true - setLastVersionDefault() + 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() + .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) + } } - func onboardingButtons() -> some View { - HStack { + func createProfileButton() -> some View { + ZStack { Button { - hideKeyboard() - withAnimation { - m.onboardingStage = .step1_SimpleXInfo - } + createProfile() } label: { - HStack { - Image(systemName: "lessthan") - Text("About SimpleX") - } - } - - Spacer() - - Button { - createProfile(displayName, showAlert: showAlert, dismiss: dismiss) - } label: { - HStack { - Text("Create") - Image(systemName: "greaterthan") - } + Text("Create profile") } + .buttonStyle(OnboardingButtonStyle(isDisabled: !canCreateProfile(displayName))) .disabled(!canCreateProfile(displayName)) + + NavigationLink(isActive: $nextStepNavLinkActive) { + nextStepDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() } } private func showAlert(_ alert: UserProfileAlert) { AlertManager.shared.showAlert(userProfileAlert(alert, $displayName)) } + + private func nextStepDestinationView() -> some View { + OnboardingConditionsView() + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground()) + } + + private func createProfile() { + hideKeyboard() + let profile = Profile( + displayName: displayName.trimmingCharacters(in: .whitespaces), + fullName: "" + ) + let m = ChatModel.shared + do { + AppChatState.shared.set(.active) + m.currentUser = try apiCreateActiveUser(profile) + try startChat(onboarding: true) + onboardingStageDefault.set(.step3_ChooseServerOperators) + nextStepNavLinkActive = true + } catch let error { + showCreateProfileAlert(showAlert: showAlert, error) + } + } } -private func createProfile(_ displayName: String, showAlert: (UserProfileAlert) -> Void, dismiss: DismissAction) { - hideKeyboard() - let profile = Profile( - displayName: displayName.trimmingCharacters(in: .whitespaces), - fullName: "" - ) +private func showCreateProfileAlert( + showAlert: (UserProfileAlert) -> Void, + _ error: Error +) { let m = ChatModel.shared - do { - AppChatState.shared.set(.active) - m.currentUser = try apiCreateActiveUser(profile) - // .isEmpty check is redundant here, but it makes it clearer what is going on - if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { - try startChat() - withAnimation { - onboardingStageDefault.set(.step3_CreateSimpleXAddress) - m.onboardingStage = .step3_CreateSimpleXAddress - } + switch error as? ChatError { + case .errorStore(.duplicateName), + .error(.userExists): + if m.currentUser == nil { + AlertManager.shared.showAlert(duplicateUserAlert) } else { - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete - dismiss() - m.users = try listUsers() - try getUserChatData() + showAlert(.duplicateUserError) } - } catch let error { - switch error as? ChatResponse { - case .chatCmdError(_, .errorStore(.duplicateName)), - .chatCmdError(_, .error(.userExists)): - if m.currentUser == nil { - AlertManager.shared.showAlert(duplicateUserAlert) - } else { - showAlert(.duplicateUserError) - } - case .chatCmdError(_, .error(.invalidDisplayName)): - if m.currentUser == nil { - AlertManager.shared.showAlert(invalidDisplayNameAlert) - } else { - showAlert(.invalidDisplayNameError) - } - default: - let err: LocalizedStringKey = "Error: \(responseError(error))" - if m.currentUser == nil { - AlertManager.shared.showAlert(creatUserErrorAlert(err)) - } else { - showAlert(.createUserError(error: err)) - } + case .error(.invalidDisplayName): + if m.currentUser == nil { + AlertManager.shared.showAlert(invalidDisplayNameAlert) + } else { + showAlert(.invalidDisplayNameError) + } + default: + let err: LocalizedStringKey = "Error: \(responseError(error))" + if m.currentUser == nil { + AlertManager.shared.showAlert(creatUserErrorAlert(err)) + } else { + showAlert(.createUserError(error: err)) } - logger.error("Failed to create user or start chat: \(responseError(error))") } + logger.error("Failed to create user or start chat: \(responseError(error))") } private func canCreateProfile(_ displayName: String) -> Bool { diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift index befb34b318..a2f5db7f03 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift @@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View { Spacer() if let userAddress = m.userAddress { - SimpleXLinkQRCode(uri: userAddress.connReqContact) + SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: Binding.constant(false)) .frame(maxHeight: g.size.width) shareQRCodeButton(userAddress) .frame(maxWidth: .infinity) @@ -77,9 +77,9 @@ struct CreateSimpleXAddress: View { progressIndicator = true Task { do { - let connReqContact = try await apiCreateUserAddress() + let connLinkContact = try await apiCreateUserAddress(short: false) DispatchQueue.main.async { - m.userAddress = UserContactLink(connReqContact: connReqContact) + m.userAddress = UserContactLink(connLinkContact: connLinkContact) } await MainActor.run { progressIndicator = false } } catch let error { @@ -121,7 +121,7 @@ struct CreateSimpleXAddress: View { private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { Button { - showShareSheet(items: [simplexChatLink(userAddress.connReqContact)]) + showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false))]) } label: { Label("Share", systemImage: "square.and.arrow.up") } @@ -189,7 +189,7 @@ struct SendAddressMailView: View { let messageBody = String(format: NSLocalizedString("""

Hi!

Connect to me via SimpleX Chat

- """, comment: "email text"), simplexChatLink(userAddress.connReqContact)) + """, comment: "email text"), simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false))) MailView( isShowing: self.$showMailView, result: $mailViewResult, diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index c1975765d2..7452d74e91 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -9,24 +9,24 @@ import SwiftUI struct HowItWorks: View { + @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var m: ChatModel var onboarding: Bool + @Binding var createProfileNavLinkActive: Bool var body: some View { VStack(alignment: .leading) { Text("How SimpleX works") .font(.largeTitle) + .bold() .padding(.vertical) ScrollView { VStack(alignment: .leading) { Group { - Text("Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*") - Text("To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.") - Text("You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them.") - Text("Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.") - if onboarding { - Text("Read more in our GitHub repository.") - } else { + Text("To protect your privacy, SimpleX uses separate IDs for each of your contacts.") + Text("Only client devices store user profiles, contacts, groups, and messages.") + Text("All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.") + if !onboarding { Text("Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme).") } } @@ -37,19 +37,34 @@ struct HowItWorks: View { Spacer() if onboarding { - OnboardingActionButton() - .padding(.bottom, 8) + VStack(spacing: 10) { + createFirstProfileButton() + onboardingButtonPlaceholder() + } } } .lineLimit(10) - .padding() + .padding(onboarding ? 25 : 16) .frame(maxHeight: .infinity, alignment: .top) .modifier(ThemedBackground()) } + + private func createFirstProfileButton() -> some View { + Button { + dismiss() + createProfileNavLinkActive = true + } label: { + Text("Create your profile") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + } } struct HowItWorks_Previews: PreviewProvider { static var previews: some View { - HowItWorks(onboarding: true) + HowItWorks( + 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 438491b5f1..8f448dc508 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -12,20 +12,39 @@ struct OnboardingView: View { var onboarding: OnboardingStage var body: some View { - switch onboarding { - case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) - case .step2_CreateProfile: CreateFirstProfile() - case .step3_CreateSimpleXAddress: CreateSimpleXAddress() - case .step4_SetNotificationsMode: SetNotificationsMode() - case .onboardingComplete: EmptyView() + NavigationView { + switch onboarding { + case .step1_SimpleXInfo: + SimpleXInfo(onboarding: true) + .modifier(ThemedBackground()) + case .step2_CreateProfile: // deprecated + CreateFirstProfile() + .modifier(ThemedBackground()) + case .step3_CreateSimpleXAddress: // deprecated + CreateSimpleXAddress() + case .step3_ChooseServerOperators: + OnboardingConditionsView() + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground()) + case .step4_SetNotificationsMode: + SetNotificationsMode() + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground()) + case .onboardingComplete: EmptyView() + } } } } +func onboardingButtonPlaceholder() -> some View { + Spacer().frame(height: 40) +} + enum OnboardingStage: String, Identifiable { case step1_SimpleXInfo - case step2_CreateProfile - case step3_CreateSimpleXAddress + case step2_CreateProfile // deprecated + case step3_CreateSimpleXAddress // deprecated + case step3_ChooseServerOperators // changed to simplified conditions case step4_SetNotificationsMode case onboardingComplete diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 7681a42a77..31865e7af9 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -13,43 +13,61 @@ struct SetNotificationsMode: View { @EnvironmentObject var m: ChatModel @State private var notificationMode = NotificationsMode.instant @State private var showAlert: NotificationAlert? + @State private var showInfo: Bool = false var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - Text("Push notifications") - .font(.largeTitle) - .bold() - .frame(maxWidth: .infinity) + GeometryReader { g in + let v = ScrollView { + VStack(alignment: .center, spacing: 20) { + Text("Push notifications") + .font(.largeTitle) + .bold() + .padding(.top, 25) + + infoText() + + Spacer() - Text("Send notifications:") - ForEach(NotificationsMode.values) { mode in - NtfModeSelector(mode: mode, selection: $notificationMode) - } - - Spacer() - - Button { - if let token = m.deviceToken { - setNotificationsMode(token, notificationMode) - } else { - AlertManager.shared.showAlertMsg(title: "No device token!") + ForEach(NotificationsMode.values) { mode in + NtfModeSelector(mode: mode, selection: $notificationMode) } - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete - } label: { - if case .off = notificationMode { - Text("Use chat") - } else { - Text("Enable notifications") + + 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 + } label: { + if case .off = notificationMode { + Text("Use chat") + } else { + Text("Enable notifications") + } + } + .buttonStyle(OnboardingButtonStyle()) + onboardingButtonPlaceholder() } } - .font(.title) - .frame(maxWidth: .infinity) + .padding(25) + .frame(minHeight: g.size.height) + } + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) } + .frame(maxHeight: .infinity) + .sheet(isPresented: $showInfo) { + NotificationsInfoView() + } + .navigationBarHidden(true) // necessary on iOS 15 } private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { @@ -75,6 +93,15 @@ struct SetNotificationsMode: View { } } } + + private func infoText() -> some View { + Button { + showInfo = true + } label: { + Label("How it affects privacy", systemImage: "info.circle") + .font(.headline) + } + } } struct NtfModeSelector: View { @@ -85,15 +112,25 @@ struct NtfModeSelector: View { var body: some View { ZStack { - VStack(alignment: .leading, spacing: 4) { - Text(mode.label) - .font(.headline) + HStack(spacing: 16) { + Image(systemName: mode.icon) + .resizable() + .scaledToFill() + .frame(width: mode.icon == "bolt" ? 14 : 18, height: 18) .foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary) - Text(ntfModeDescription(mode)) - .lineLimit(10) - .font(.subheadline) + VStack(alignment: .leading, spacing: 4) { + Text(mode.label) + .font(.headline) + .foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary) + Text(ntfModeShortDescription(mode)) + .lineLimit(2) + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + } } - .padding(12) + .padding(.vertical, 12) + .padding(.trailing, 12) + .padding(.leading, 16) } .frame(maxWidth: .infinity, alignment: .leading) .background(tapped ? Color(uiColor: .secondarySystemFill) : theme.colors.background) @@ -109,6 +146,37 @@ struct NtfModeSelector: View { } } +struct NotificationsInfoView: View { + var body: some View { + VStack(alignment: .leading) { + Text("Notifications privacy") + .font(.largeTitle) + .bold() + .padding(.vertical) + ScrollView { + VStack(alignment: .leading) { + Group { + ForEach(NotificationsMode.values) { mode in + VStack(alignment: .leading, spacing: 4) { + (Text(Image(systemName: mode.icon)) + textSpace + Text(mode.label)) + .font(.headline) + .foregroundColor(.secondary) + Text(ntfModeDescription(mode)) + .lineLimit(10) + .font(.callout) + } + } + } + .padding(.bottom) + } + } + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .modifier(ThemedBackground()) + } +} + struct NotificationsModeView_Previews: PreviewProvider { static var previews: some View { SetNotificationsMode() diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index ee5a618e68..9f41a37b1d 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -13,59 +13,62 @@ struct SimpleXInfo: View { @EnvironmentObject var m: ChatModel @Environment(\.colorScheme) var colorScheme: ColorScheme @State private var showHowItWorks = false + @State private var createProfileNavLinkActive = false var onboarding: Bool var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .leading) { - Image(colorScheme == .light ? "logo" : "logo-light") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: g.size.width * 0.67) - .padding(.bottom, 8) - .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) - - VStack(alignment: .leading) { - Text("The next generation of private messaging") - .font(.title2) - .padding(.bottom, 30) - .padding(.horizontal, 40) - .frame(maxWidth: .infinity) - .multilineTextAlignment(.center) - infoRow("privacy", "Privacy redefined", - "The 1st platform without any user identifiers – private by design.", width: 48) - infoRow("shield", "Immune to spam and abuse", - "People can connect to you only via the links you share.", width: 46) - infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", - "Open-source protocol and code – anybody can run the servers.", width: 44) + VStack(alignment: .center, spacing: 10) { + Image(colorScheme == .light ? "logo" : "logo-light") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: g.size.width * 0.67) + .padding(.bottom, 8) + .padding(.leading, 4) + .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) + + Button { + showHowItWorks = true + } label: { + Label("The future of messaging", systemImage: "info.circle") + .font(.headline) + } } Spacer() + + VStack(alignment: .leading) { + onboardingInfoRow("privacy", "Privacy redefined", + "No user identifiers.", width: 48) + onboardingInfoRow("shield", "Immune to spam", + "You decide who can connect.", width: 46) + onboardingInfoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", + "Anybody can host servers.", width: 46) + } + .padding(.leading, 16) + + Spacer() + if onboarding { - OnboardingActionButton() - Spacer() + VStack(spacing: 10) { + createFirstProfileButton() - Button { - m.migrationState = .pasteOrScanLink - } label: { - Label("Migrate from another device", systemImage: "tray.and.arrow.down") - .font(.subheadline) + Button { + m.migrationState = .pasteOrScanLink + } label: { + Label("Migrate from another device", systemImage: "tray.and.arrow.down") + .font(.system(size: 17, weight: .semibold)) + .frame(minHeight: 40) + } + .frame(maxWidth: .infinity) } - .padding(.bottom, 8) - .frame(maxWidth: .infinity) } - - Button { - showHowItWorks = true - } label: { - Label("How it works", systemImage: "info.circle") - .font(.subheadline) - } - .padding(.bottom, 8) - .frame(maxWidth: .infinity) - } + .padding(.horizontal, 25) + .padding(.top, 75) + .padding(.bottom, 25) .frame(minHeight: g.size.height) } .sheet(isPresented: Binding( @@ -83,76 +86,68 @@ struct SimpleXInfo: View { } } .sheet(isPresented: $showHowItWorks) { - HowItWorks(onboarding: onboarding) + HowItWorks( + onboarding: onboarding, + createProfileNavLinkActive: $createProfileNavLinkActive + ) + } + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v } } + .onAppear() { + setLastVersionDefault() + } .frame(maxHeight: .infinity) - .padding() + .navigationBarHidden(true) // necessary on iOS 15 } - private func infoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { + private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { HStack(alignment: .top) { Image(image) .resizable() .scaledToFit() .frame(width: width, height: 54) .frame(width: 54) - .padding(.top, 4) - .padding(.leading, 4) .padding(.trailing, 10) VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) Text(text).frame(minHeight: 40, alignment: .top) + .font(.callout) + .lineLimit(3) + .fixedSize(horizontal: false, vertical: true) } + .padding(.top, 4) + } + .padding(.bottom, 12) + } + + private func createFirstProfileButton() -> some View { + ZStack { + Button { + createProfileNavLinkActive = true + } label: { + Text("Create your profile") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + + NavigationLink(isActive: $createProfileNavLinkActive) { + CreateFirstProfile() + .modifier(ThemedBackground()) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() } - .padding(.bottom, 20) - .padding(.trailing, 6) } } -struct OnboardingActionButton: View { - @EnvironmentObject var m: ChatModel - @Environment(\.colorScheme) var colorScheme +let textSpace = Text(verbatim: " ") - var body: some View { - if m.currentUser == nil { - actionButton("Create your profile", onboarding: .step2_CreateProfile) - } else { - actionButton("Make a private connection", onboarding: .onboardingComplete) - } - } - - private func actionButton(_ label: LocalizedStringKey, onboarding: OnboardingStage) -> some View { - Button { - withAnimation { - onboardingStageDefault.set(onboarding) - m.onboardingStage = onboarding - } - } label: { - HStack { - Text(label).font(.title2) - Image(systemName: "greaterthan") - } - } - .frame(maxWidth: .infinity) - .padding(.bottom) - } - - private func actionButton(_ label: LocalizedStringKey, action: @escaping () -> Void) -> some View { - Button { - withAnimation { - action() - } - } label: { - HStack { - Text(label).font(.title2) - Image(systemName: "greaterthan") - } - } - .frame(maxWidth: .infinity) - .padding(.bottom) - } -} +let textNewLine = Text(verbatim: "\n") struct SimpleXInfo_Previews: PreviewProvider { static var previews: some View { diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index ed3adcfe7d..f65a21623a 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -7,190 +7,209 @@ // import SwiftUI +import SimpleXChat private struct VersionDescription { var version: String var post: URL? - var features: [FeatureDescription] + var features: [Feature] } -private struct FeatureDescription { - var icon: String? - var title: LocalizedStringKey - var description: LocalizedStringKey? +private enum Feature: Identifiable { + case feature(Description) + case view(FeatureView) + + var id: LocalizedStringKey { + switch self { + case let .feature(d): d.title + case let .view(v): v.title + } + } +} + +private struct Description { + let icon: String? + let title: LocalizedStringKey + let description: LocalizedStringKey? var subfeatures: [(icon: String, description: LocalizedStringKey)] = [] } +private struct FeatureView { + let icon: String? + let title: LocalizedStringKey + let view: () -> any View +} + private let versionDescriptions: [VersionDescription] = [ VersionDescription( version: "v4.2", post: URL(string: "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html"), features: [ - FeatureDescription( + .feature(Description( icon: "checkmark.shield", title: "Security assessment", description: "SimpleX Chat security was audited by Trail of Bits." - ), - FeatureDescription( + )), + .feature(Description( icon: "person.2", title: "Group links", description: "Admins can create the links to join groups." - ), - FeatureDescription( + )), + .feature(Description( icon: "checkmark", title: "Auto-accept contact requests", description: "With optional welcome message." - ), + )), ] ), VersionDescription( version: "v4.3", post: URL(string: "https://simplex.chat/blog/20221206-simplex-chat-v4.3-voice-messages.html"), features: [ - FeatureDescription( + .feature(Description( icon: "mic", title: "Voice messages", description: "Max 30 seconds, received instantly." - ), - FeatureDescription( + )), + .feature(Description( icon: "trash.slash", title: "Irreversible message deletion", description: "Your contacts can allow full message deletion." - ), - FeatureDescription( + )), + .feature(Description( icon: "externaldrive.connected.to.line.below", title: "Improved server configuration", description: "Add servers by scanning QR codes." - ), - FeatureDescription( + )), + .feature(Description( icon: "eye.slash", title: "Improved privacy and security", description: "Hide app screen in the recent apps." - ), + )), ] ), VersionDescription( version: "v4.4", post: URL(string: "https://simplex.chat/blog/20230103-simplex-chat-v4.4-disappearing-messages.html"), features: [ - FeatureDescription( + .feature(Description( icon: "stopwatch", title: "Disappearing messages", description: "Sent messages will be deleted after set time." - ), - FeatureDescription( + )), + .feature(Description( icon: "ellipsis.circle", title: "Live messages", description: "Recipients see updates as you type them." - ), - FeatureDescription( + )), + .feature(Description( icon: "checkmark.shield", title: "Verify connection security", description: "Compare security codes with your contacts." - ), - FeatureDescription( + )), + .feature(Description( icon: "camera", title: "GIFs and stickers", description: "Send them from gallery or custom keyboards." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "French interface", description: "Thanks to the users – contribute via Weblate!" - ) + )), ] ), VersionDescription( version: "v4.5", post: URL(string: "https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html"), features: [ - FeatureDescription( + .feature(Description( icon: "person.crop.rectangle.stack", title: "Multiple chat profiles", description: "Different names, avatars and transport isolation." - ), - FeatureDescription( + )), + .feature(Description( icon: "rectangle.and.pencil.and.ellipsis", title: "Message draft", description: "Preserve the last message draft, with attachments." - ), - FeatureDescription( + )), + .feature(Description( icon: "network.badge.shield.half.filled", title: "Transport isolation", description: "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." - ), - FeatureDescription( + )), + .feature(Description( icon: "lock.doc", title: "Private filenames", description: "To protect timezone, image/voice files use UTC." - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.25", title: "Reduced battery usage", description: "More improvements are coming soon!" - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Italian interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ) + )), ] ), VersionDescription( version: "v4.6", post: URL(string: "https://simplex.chat/blog/20230328-simplex-chat-v4-6-hidden-profiles.html"), features: [ - FeatureDescription( + .feature(Description( icon: "lock", title: "Hidden chat profiles", description: "Protect your chat profiles with a password!" - ), - FeatureDescription( + )), + .feature(Description( icon: "phone.arrow.up.right", title: "Audio and video calls", description: "Fully re-implemented - work in background!" - ), - FeatureDescription( + )), + .feature(Description( icon: "flag", title: "Group moderation", description: "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" - ), - FeatureDescription( + )), + .feature(Description( icon: "plus.message", title: "Group welcome message", description: "Set the message shown to new members!" - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.50", title: "Further reduced battery usage", description: "More improvements are coming soon!" - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Chinese and Spanish interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.0", post: URL(string: "https://simplex.chat/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.html"), features: [ - FeatureDescription( + .feature(Description( icon: "arrow.up.doc", title: "Videos and files up to 1gb", description: "Fast and no wait until the sender is online!" - ), - FeatureDescription( + )), + .feature(Description( icon: "lock", title: "App passcode", description: "Set it instead of system authentication." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Polish interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), // Also @@ -200,240 +219,240 @@ private let versionDescriptions: [VersionDescription] = [ version: "v5.1", post: URL(string: "https://simplex.chat/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.html"), features: [ - FeatureDescription( + .feature(Description( icon: "face.smiling", title: "Message reactions", description: "Finally, we have them! 🚀" - ), - FeatureDescription( + )), + .feature(Description( icon: "arrow.up.message", title: "Better messages", description: "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." - ), - FeatureDescription( + )), + .feature(Description( icon: "lock", title: "Self-destruct passcode", description: "All data is erased when it is entered." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Japanese interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.2", post: URL(string: "https://simplex.chat/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.html"), features: [ - FeatureDescription( + .feature(Description( icon: "checkmark", title: "Message delivery receipts!", description: "The second tick we missed! ✅" - ), - FeatureDescription( + )), + .feature(Description( icon: "star", title: "Find chats faster", description: "Filter unread and favorite chats." - ), - FeatureDescription( + )), + .feature(Description( icon: "exclamationmark.arrow.triangle.2.circlepath", title: "Keep your connections", description: "Fix encryption after restoring backups." - ), - FeatureDescription( + )), + .feature(Description( icon: "stopwatch", title: "Make one message disappear", description: "Even when disabled in the conversation." - ), - FeatureDescription( + )), + .feature(Description( icon: "gift", title: "A few more things", description: "- more stable message delivery.\n- a bit better groups.\n- and more!" - ), + )), ] ), VersionDescription( version: "v5.3", post: URL(string: "https://simplex.chat/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.html"), features: [ - FeatureDescription( + .feature(Description( icon: "desktopcomputer", title: "New desktop app!", description: "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" - ), - FeatureDescription( + )), + .feature(Description( icon: "lock", title: "Encrypt stored files & media", description: "App encrypts new local files (except videos)." - ), - FeatureDescription( + )), + .feature(Description( icon: "magnifyingglass", title: "Discover and join groups", description: "- 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." - ), - FeatureDescription( + )), + .feature(Description( icon: "theatermasks", title: "Simplified incognito mode", description: "Toggle incognito when connecting." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "\(4) new interface languages", description: "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.4", post: URL(string: "https://simplex.chat/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.html"), features: [ - FeatureDescription( + .feature(Description( icon: "desktopcomputer", title: "Link mobile and desktop apps! 🔗", description: "Via secure quantum resistant protocol." - ), - FeatureDescription( + )), + .feature(Description( icon: "person.2", title: "Better groups", description: "Faster joining and more reliable messages." - ), - FeatureDescription( + )), + .feature(Description( icon: "theatermasks", title: "Incognito groups", description: "Create a group using a random profile." - ), - FeatureDescription( + )), + .feature(Description( icon: "hand.raised", title: "Block group members", description: "To hide unwanted messages." - ), - FeatureDescription( + )), + .feature(Description( icon: "gift", title: "A few more things", description: "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" - ), + )), ] ), VersionDescription( version: "v5.5", post: URL(string: "https://simplex.chat/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.html"), features: [ - FeatureDescription( + .feature(Description( icon: "folder", title: "Private notes", description: "With encrypted files and media." - ), - FeatureDescription( + )), + .feature(Description( icon: "link", title: "Paste link to connect!", description: "Search bar accepts invitation links." - ), - FeatureDescription( + )), + .feature(Description( icon: "bubble.left.and.bubble.right", title: "Join group conversations", description: "Recent history and improved [directory bot](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)." - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.50", title: "Improved message delivery", description: "With reduced battery usage." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Turkish interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.6", post: URL(string: "https://simplex.chat/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html"), features: [ - FeatureDescription( + .feature(Description( icon: "key", title: "Quantum resistant encryption", description: "Enable in direct chats (BETA)!" - ), - FeatureDescription( + )), + .feature(Description( icon: "tray.and.arrow.up", title: "App data migration", description: "Migrate to another device via QR code." - ), - FeatureDescription( + )), + .feature(Description( icon: "phone", title: "Picture-in-picture calls", description: "Use the app while in the call." - ), - FeatureDescription( + )), + .feature(Description( icon: "hand.raised", title: "Safer groups", description: "Admins can block a member for all." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Hungarian interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.7", post: URL(string: "https://simplex.chat/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.html"), features: [ - FeatureDescription( + .feature(Description( icon: "key", title: "Quantum resistant encryption", description: "Will be enabled in direct chats!" - ), - FeatureDescription( + )), + .feature(Description( icon: "arrowshape.turn.up.forward", title: "Forward and save messages", description: "Message source remains private." - ), - FeatureDescription( + )), + .feature(Description( icon: "music.note", title: "In-call sounds", description: "When connecting audio and video calls." - ), - FeatureDescription( + )), + .feature(Description( icon: "person.crop.square", title: "Shape profile images", description: "Square, circle, or anything in between." - ), - FeatureDescription( + )), + .feature(Description( icon: "antenna.radiowaves.left.and.right", title: "Network management", description: "More reliable network connection." - ) + )), ] ), VersionDescription( version: "v5.8", post: URL(string: "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html"), features: [ - FeatureDescription( + .feature(Description( icon: "arrow.forward", title: "Private message routing 🚀", description: "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." - ), - FeatureDescription( + )), + .feature(Description( icon: "network.badge.shield.half.filled", title: "Safely receive files", description: "Confirm files from unknown servers." - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.50", title: "Improved message delivery", description: "With reduced battery usage." - ) + )), ] ), VersionDescription( version: "v6.0", post: URL(string: "https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html"), features: [ - FeatureDescription( + .feature(Description( icon: nil, title: "New chat experience 🎉", description: nil, @@ -444,8 +463,8 @@ private let versionDescriptions: [VersionDescription] = [ ("platter.filled.bottom.and.arrow.down.iphone", "Use the app with one hand."), ("paintpalette", "Color chats with the new themes."), ] - ), - FeatureDescription( + )), + .feature(Description( icon: nil, title: "New media options", description: nil, @@ -454,17 +473,110 @@ private let versionDescriptions: [VersionDescription] = [ ("play.circle", "Play from the chat list."), ("circle.filled.pattern.diagonalline.rectangle", "Blur for better privacy.") ] - ), - FeatureDescription( + )), + .feature(Description( icon: "arrow.forward", title: "Private message routing 🚀", description: "It protects your IP address and connections." - ), - FeatureDescription( + )), + .feature(Description( icon: "network", title: "Better networking", description: "Connection and servers status." - ) + )), + ] + ), + VersionDescription( + version: "v6.1", + post: URL(string: "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"), + features: [ + .feature(Description( + icon: "checkmark.shield", + title: "Better security ✅", + description: "SimpleX protocols reviewed by Trail of Bits." + )), + .feature(Description( + icon: "video", + title: "Better calls", + description: "Switch audio and video during the call." + )), + .feature(Description( + icon: "bolt", + title: "Better notifications", + description: "Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" + )), + .feature(Description( + icon: nil, + title: "Better user experience", + description: nil, + subfeatures: [ + ("link", "Switch chat profile for 1-time invitations."), + ("message", "Customizable message shape."), + ("calendar", "Better message dates."), + ("arrowshape.turn.up.right", "Forward up to 20 messages at once."), + ("flag", "Delete or moderate up to 200 messages.") + ] + )), + ] + ), + VersionDescription( + version: "v6.2", + post: URL(string: "https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html"), + features: [ + .view(FeatureView( + icon: nil, + title: "Network decentralization", + view: { NewOperatorsView() } + )), + .feature(Description( + icon: "briefcase", + title: "Business chats", + description: "Privacy for your customers." + )), + .feature(Description( + icon: "bolt", + title: "More reliable notifications", + description: "Delivered even when Apple drops them." + )), + ] + ), + VersionDescription( + version: "v6.3", + post: URL(string: "https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html"), + features: [ + .feature(Description( + icon: "at", + title: "Mention members 👋", + description: "Get notified when mentioned." + )), + .feature(Description( + icon: "flag", + title: "Send private reports", + description: "Help admins moderating their groups." + )), + .feature(Description( + icon: "list.bullet", + title: "Organize chats into lists", + description: "Don't miss important messages." + )), + .feature(Description( + icon: nil, + title: "Better privacy and security", + description: nil, + subfeatures: [ + ("eye.slash", "Private media file names."), + ("trash", "Set message expiration in chats.") + ] + )), + .feature(Description( + icon: nil, + title: "Better groups performance", + description: nil, + subfeatures: [ + ("bolt", "Faster sending messages."), + ("person.2.slash", "Faster deletion of groups.") + ] + )), ] ), ] @@ -481,14 +593,57 @@ func shouldShowWhatsNew() -> Bool { return v != lastVersion } +fileprivate struct NewOperatorsView: View { + var body: some View { + VStack(alignment: .leading) { + Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) + .resizable() + .scaledToFit() + .frame(height: 48) + Text("The second preset operator in the app!") + .multilineTextAlignment(.leading) + .lineLimit(10) + HStack { + Text("Enable Flux in Network & servers settings for better metadata privacy.") + } + } + } +} + +private enum WhatsNewViewSheet: Identifiable { + case showConditions + + var id: String { + switch self { + case .showConditions: return "showConditions" + } + } +} + struct WhatsNewView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @State var currentVersion = versionDescriptions.count - 1 @State var currentVersionNav = versionDescriptions.count - 1 var viaSettings = false + var updatedConditions: Bool + @State private var sheetItem: WhatsNewViewSheet? = nil var body: some View { + whatsNewView() + .sheet(item: $sheetItem) { item in + switch item { + case .showConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) + } + } + } + + private func whatsNewView() -> some View { VStack { TabView(selection: $currentVersion) { ForEach(Array(versionDescriptions.enumerated()), id: \.0) { (i, v) in @@ -499,9 +654,11 @@ struct WhatsNewView: View { .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity) .padding(.vertical) - ForEach(v.features, id: \.title) { f in - featureDescription(f) - .padding(.bottom, 8) + ForEach(v.features) { f in + switch f { + case let .feature(d): featureDescription(d).padding(.bottom, 8) + case let .view(v): AnyView(v.view()).padding(.bottom, 8) + } } if let post = v.post { Link(destination: post) { @@ -511,13 +668,20 @@ struct WhatsNewView: View { } } } + if updatedConditions { + Button("View updated conditions") { + sheetItem = .showConditions + } + } if !viaSettings { Spacer() + Button("Ok") { dismiss() } .font(.title3) .frame(maxWidth: .infinity, alignment: .center) + Spacer() } } @@ -535,20 +699,24 @@ struct WhatsNewView: View { currentVersionNav = currentVersion } } - - private func featureDescription(_ f: FeatureDescription) -> some View { - VStack(alignment: .leading, spacing: 4) { - if let icon = f.icon { - HStack(alignment: .center, spacing: 4) { - Image(systemName: icon) - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - .frame(minWidth: 30, alignment: .center) - Text(f.title).font(.title3).bold() - } - } else { - Text(f.title).font(.title3).bold() + + @ViewBuilder private func featureHeader(_ icon: String?, _ title: LocalizedStringKey) -> some View { + if let icon { + HStack(alignment: .center, spacing: 4) { + Image(systemName: icon) + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(minWidth: 30, alignment: .center) + Text(title).font(.title3).bold() } + } else { + Text(title).font(.title3).bold() + } + } + + private func featureDescription(_ f: Description) -> some View { + VStack(alignment: .leading, spacing: 4) { + featureHeader(f.icon, f.title) if let d = f.description { Text(d) .multilineTextAlignment(.leading) @@ -603,6 +771,6 @@ struct WhatsNewView: View { struct NewFeaturesView_Previews: PreviewProvider { static var previews: some View { - WhatsNewView() + WhatsNewView(updatedConditions: false) } } diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift index be063334d3..01b25baed8 100644 --- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift +++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift @@ -14,7 +14,6 @@ struct ConnectDesktopView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction - var viaSettings = false @AppStorage(DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS) private var deviceName = UIDevice.current.name @AppStorage(DEFAULT_CONFIRM_REMOTE_SESSIONS) private var confirmRemoteSessions = false @AppStorage(DEFAULT_CONNECT_REMOTE_VIA_MULTICAST) private var connectRemoteViaMulticast = true @@ -57,23 +56,6 @@ struct ConnectDesktopView: View { } var body: some View { - if viaSettings { - viewBody - .modifier(BackButton(label: "Back", disabled: Binding.constant(false)) { - if m.activeRemoteCtrl { - alert = .disconnectDesktop(action: .back) - } else { - dismiss() - } - }) - } else { - NavigationView { - viewBody - } - } - } - - var viewBody: some View { Group { let discovery = m.remoteCtrlSession?.discovery if discovery == true || (discovery == nil && !showConnectScreen) { @@ -286,7 +268,7 @@ struct ConnectDesktopView: View { private func ctrlDeviceNameText(_ session: RemoteCtrlSession, _ rc: RemoteCtrlInfo?) -> Text { var t = Text(rc?.deviceViewName ?? session.ctrlAppInfo?.deviceName ?? "") if (rc == nil) { - t = t + Text(" ") + Text("(new)").italic() + t = t + textSpace + Text("(new)").italic() } return t } @@ -295,7 +277,7 @@ struct ConnectDesktopView: View { let v = session.ctrlAppInfo?.appVersionRange.maxVersion var t = Text("v\(v ?? "")") if v != session.appVersion { - t = t + Text(" ") + Text("(this device v\(session.appVersion))").italic() + t = t + textSpace + Text("(this device v\(session.appVersion))").italic() } return t } @@ -474,12 +456,12 @@ struct ConnectDesktopView: View { } } catch let e { await MainActor.run { - switch e as? ChatResponse { - case .chatCmdError(_, .errorRemoteCtrl(.badInvitation)): alert = .badInvitationError - case .chatCmdError(_, .error(.commandError)): alert = .badInvitationError - case let .chatCmdError(_, .errorRemoteCtrl(.badVersion(v))): alert = .badVersionError(version: v) - case .chatCmdError(_, .errorAgent(.RCP(.version))): alert = .badVersionError(version: nil) - case .chatCmdError(_, .errorAgent(.RCP(.ctrlAuth))): alert = .desktopDisconnectedError + switch e as? ChatError { + case .errorRemoteCtrl(.badInvitation): alert = .badInvitationError + case .error(.commandError): alert = .badInvitationError + case let .errorRemoteCtrl(.badVersion(v)): alert = .badVersionError(version: v) + case .errorAgent(.RCP(.version)): alert = .badVersionError(version: nil) + case .errorAgent(.RCP(.ctrlAuth)): alert = .desktopDisconnectedError default: errorAlert(e) } } diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index d209ced128..554219eb69 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -18,7 +18,9 @@ struct TerminalView: View { @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @State var composeState: ComposeState = ComposeState() + @State var selectedRange = NSRange() @State private var keyboardVisible = false + @State private var keyboardHiddenDate = Date.now @State var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) @State private var terminalItem: TerminalItem? @State private var scrolled = false @@ -96,16 +98,23 @@ struct TerminalView: View { SendMessageView( composeState: $composeState, + selectedRange: $selectedRange, sendMessage: { _ in consoleSendMessage() }, showVoiceMessageButton: false, onMediaAdded: { _ in }, - keyboardVisible: $keyboardVisible + keyboardVisible: $keyboardVisible, + keyboardHiddenDate: $keyboardHiddenDate ) .padding(.horizontal, 12) } } .navigationViewStyle(.stack) - .navigationTitle("Chat console") + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Chat console").font(.headline) + } + } .modifier(ThemedBackground()) } @@ -136,18 +145,18 @@ struct TerminalView: View { } func consoleSendMessage() { - let cmd = ChatCommand.string(composeState.message) if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) { - let resp = ChatResponse.chatCmdError(user_: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty"))) + let resp: APIResult = APIResult.error(ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty"))) Task { - await TerminalItems.shared.addCommand(.now, cmd, resp) + await TerminalItems.shared.addCommand(.now, .string(composeState.message), resp) } } else { + let cmd = composeState.message DispatchQueue.global().async { Task { - composeState.inProgress = true - _ = await chatSendCmd(cmd) - composeState.inProgress = false + await MainActor.run { composeState.inProgress = true } + await sendTerminalCmd(cmd) + await MainActor.run { composeState.inProgress = false } } } } @@ -155,12 +164,38 @@ struct TerminalView: View { } } +func sendTerminalCmd(_ cmd: String) async { + let start: Date = .now + await withCheckedContinuation { (cont: CheckedContinuation) in + let d = sendSimpleXCmdStr(cmd) + Task { + guard let d else { + await TerminalItems.shared.addCommand(start, ChatCommand.string(cmd), APIResult.error(.invalidJSON(json: nil))) + return + } + let r0: APIResult = decodeAPIResult(d) + guard case .invalid = r0 else { + await TerminalItems.shared.addCommand(start, .string(cmd), r0) + return + } + let r1: APIResult = decodeAPIResult(d) + guard case .invalid = r1 else { + await TerminalItems.shared.addCommand(start, .string(cmd), r1) + return + } + let r2: APIResult = decodeAPIResult(d) + await TerminalItems.shared.addCommand(start, .string(cmd), r2) + } + cont.resume(returning: ()) + } +} + struct TerminalView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.terminalItems = [ - .resp(.now, ChatResponse.response(type: "contactSubscribed", json: "{}")), - .resp(.now, ChatResponse.response(type: "newChatItem", json: "{}")) + .err(.now, APIResult.invalid(type: "contactSubscribed", json: "{}".data(using: .utf8)!).unexpected), + .err(.now, APIResult.invalid(type: "newChatItems", json: "{}".data(using: .utf8)!).unexpected) ] return NavigationView { TerminalView() diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index 6247777bf2..44e0b20958 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -19,9 +19,15 @@ extension AppSettings { val.hostMode = .publicHost val.requiredHostMode = true } - val.socksProxy = nil - setNetCfg(val) + if val.socksProxy != nil { + val.socksProxy = networkProxy?.toProxyString() + setNetCfg(val, networkProxy: networkProxy) + } else { + val.socksProxy = nil + setNetCfg(val, networkProxy: nil) + } } + if let val = networkProxy { networkProxyDefault.set(val) } if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) } if let val = privacyAcceptImages { @@ -52,17 +58,21 @@ extension AppSettings { profileImageCornerRadiusGroupDefault.set(val) def.setValue(val, forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) } - if let val = uiColorScheme { def.setValue(val, forKey: DEFAULT_CURRENT_THEME) } - if let val = uiDarkColorScheme { def.setValue(val, forKey: DEFAULT_SYSTEM_DARK_THEME) } - if let val = uiCurrentThemeIds { def.setValue(val, forKey: DEFAULT_CURRENT_THEME_IDS) } - if let val = uiThemes { def.setValue(val.skipDuplicates(), forKey: DEFAULT_THEME_OVERRIDES) } + if let val = uiChatItemRoundness { def.setValue(val, forKey: DEFAULT_CHAT_ITEM_ROUNDNESS)} + if let val = uiChatItemTail { def.setValue(val, forKey: DEFAULT_CHAT_ITEM_TAIL)} + if let val = uiColorScheme { currentThemeDefault.set(val) } + if let val = uiDarkColorScheme { systemDarkThemeDefault.set(val) } + if let val = uiCurrentThemeIds { currentThemeIdsDefault.set(val) } + if let val = uiThemes { themeOverridesDefault.set(val.skipDuplicates()) } if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) } + if let val = chatBottomBar { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) } } public static var current: AppSettings { let def = UserDefaults.standard var c = AppSettings.defaults c.networkConfig = getNetCfg() + c.networkProxy = networkProxyDefault.get() c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() @@ -84,11 +94,14 @@ extension AppSettings { c.iosCallKitEnabled = callKitEnabledGroupDefault.get() c.iosCallKitCallsInRecents = def.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) c.uiProfileImageCornerRadius = def.double(forKey: DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) + c.uiChatItemRoundness = def.double(forKey: DEFAULT_CHAT_ITEM_ROUNDNESS) + c.uiChatItemTail = def.bool(forKey: DEFAULT_CHAT_ITEM_TAIL) c.uiColorScheme = currentThemeDefault.get() c.uiDarkColorScheme = systemDarkThemeDefault.get() c.uiCurrentThemeIds = currentThemeIdsDefault.get() c.uiThemes = themeOverridesDefault.get() c.oneHandUI = groupDefaults.bool(forKey: GROUP_DEFAULT_ONE_HAND_UI) + c.chatBottomBar = groupDefaults.bool(forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) return c } } diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 73a789f108..c6d0e27289 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -33,6 +33,8 @@ struct AppearanceSettings: View { }() @State private var darkModeTheme: String = UserDefaults.standard.string(forKey: DEFAULT_SYSTEM_DARK_THEME) ?? DefaultTheme.DARK.themeName @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileImageCornerRadius = defaultProfileImageCorner + @AppStorage(DEFAULT_CHAT_ITEM_ROUNDNESS) private var chatItemRoundness = defaultChatItemRoundness + @AppStorage(DEFAULT_CHAT_ITEM_TAIL) private var chatItemTail = true @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @@ -179,6 +181,14 @@ struct AppearanceSettings: View { } } + Section(header: Text("Message shape").foregroundColor(theme.colors.secondary)) { + HStack { + Text("Corner") + Slider(value: $chatItemRoundness, in: 0...1, step: 0.05) + } + Toggle("Tail", isOn: $chatItemTail) + } + Section(header: Text("Profile images").foregroundColor(theme.colors.secondary)) { HStack(spacing: 16) { if let img = m.currentUser?.image, img != "" { @@ -357,21 +367,22 @@ struct ChatThemePreview: View { let alice = ChatItem.getSample(1, CIDirection.directRcv, Date.now, NSLocalizedString("Good afternoon!", comment: "message preview")) let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir)) HStack { - ChatItemView(chat: Chat.sampleData, chatItem: alice, revealed: Binding.constant(false)) - .modifier(ChatItemClipped()) + ChatItemView(chat: Chat.sampleData, chatItem: alice, scrollToItemId: { _ in }) + .modifier(ChatItemClipped(alice, tailVisible: true)) Spacer() } HStack { Spacer() - ChatItemView(chat: Chat.sampleData, chatItem: bob, revealed: Binding.constant(false)) - .modifier(ChatItemClipped()) + ChatItemView(chat: Chat.sampleData, chatItem: bob, scrollToItemId: { _ in }) + .modifier(ChatItemClipped(bob, tailVisible: true)) .frame(alignment: .trailing) } } else { Rectangle().fill(.clear) } } - .padding(10) + .padding(.vertical, 10) + .padding(.horizontal, 16) .frame(maxWidth: .infinity) if let wallpaperType, let wallpaperImage = wallpaperType.image, let backgroundColor, let tintColor { @@ -572,11 +583,14 @@ struct CustomizeThemeView: View { } } - ImportExportThemeSection(perChat: nil, perUser: nil, save: { theme in + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: nil) + } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { theme in ThemeManager.saveAndApplyThemeOverrides(theme) saveThemeToDatabase(nil) - }) - } + } + ) /// When changing app theme, user overrides are hidden. User overrides will be returned back after closing Appearance screen, see ThemeDestinationPicker() .interactiveDismissDisabled(true) } @@ -584,10 +598,9 @@ struct CustomizeThemeView: View { struct ImportExportThemeSection: View { @EnvironmentObject var theme: AppTheme + @Binding var showFileImporter: Bool var perChat: ThemeModeOverride? var perUser: ThemeModeOverrides? - var save: (ThemeOverrides) -> Void - @State private var showFileImporter = false var body: some View { Section { @@ -615,39 +628,47 @@ struct ImportExportThemeSection: View { } label: { Text("Import theme").foregroundColor(theme.colors.primary) } - .fileImporter( - isPresented: $showFileImporter, - allowedContentTypes: [.data/*.plainText*/], - allowsMultipleSelection: false - ) { result in - if case let .success(files) = result, let fileURL = files.first { - do { - var fileSize: Int? = nil - if fileURL.startAccessingSecurityScopedResource() { - let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) - fileSize = resourceValues.fileSize - } - if let fileSize = fileSize, - // Same as Android/desktop - fileSize <= 5_500_000 { - if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { - save(theme) - logger.error("Saved theme from file") - } else { - logger.error("Error decoding theme file") - } - fileURL.stopAccessingSecurityScopedResource() - } else { - fileURL.stopAccessingSecurityScopedResource() - let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary) - AlertManager.shared.showAlertMsg( - title: "Large file!", - message: "Currently maximum supported file size is \(prettyMaxFileSize)." - ) - } - } catch { - logger.error("Appearance fileImporter error \(error.localizedDescription)") + } + } +} + +struct ThemeImporter: ViewModifier { + @Binding var isPresented: Bool + var save: (ThemeOverrides) -> Void + + func body(content: Content) -> some View { + content.fileImporter( + isPresented: $isPresented, + allowedContentTypes: [.data/*.plainText*/], + allowsMultipleSelection: false + ) { result in + if case let .success(files) = result, let fileURL = files.first { + do { + var fileSize: Int? = nil + if fileURL.startAccessingSecurityScopedResource() { + let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) + fileSize = resourceValues.fileSize } + if let fileSize = fileSize, + // Same as Android/desktop + fileSize <= 5_500_000 { + if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { + save(theme) + logger.error("Saved theme from file") + } else { + logger.error("Error decoding theme file") + } + fileURL.stopAccessingSecurityScopedResource() + } else { + fileURL.stopAccessingSecurityScopedResource() + let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary) + AlertManager.shared.showAlertMsg( + title: "Large file!", + message: "Currently maximum supported file size is \(prettyMaxFileSize)." + ) + } + } catch { + logger.error("Appearance fileImporter error \(error.localizedDescription)") } } } @@ -797,7 +818,7 @@ struct ThemeDestinationPicker: View { @Binding var customizeThemeIsOpen: Bool var body: some View { - let values = [(nil, "All profiles")] + m.users.filter { $0.user.activeUser }.map { ($0.user.userId, $0.user.chatViewName)} + let values = [(nil, NSLocalizedString("All profiles", comment: "profile dropdown"))] + m.users.filter { $0.user.activeUser }.map { ($0.user.userId, $0.user.chatViewName)} if values.contains(where: { (userId, text) in userId == themeUserDestination?.0 }) { Picker("Apply to", selection: $themeUserDest) { diff --git a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift index 4ef05bd998..54454b7cef 100644 --- a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift +++ b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift @@ -45,7 +45,7 @@ struct DeveloperView: View { } header: { Text("") } footer: { - ((developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option.")) + ((developerTools ? Text("Show:") : Text("Hide:")) + textSpace + Text("Database IDs and Transport isolation option.")) .foregroundColor(theme.colors.secondary) } @@ -54,6 +54,13 @@ struct DeveloperView: View { settingsRow("internaldrive", color: theme.colors.secondary) { Toggle("Confirm database upgrades", isOn: $confirmDatabaseUpgrades) } + NavigationLink { + StorageView() + .navigationTitle("Storage") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("internaldrive", color: theme.colors.secondary) { Text("Storage") } + } } header: { Text("Developer options") } diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift index a0250afddf..d9862aaac8 100644 --- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift @@ -26,6 +26,7 @@ struct IncognitoHelp: View { Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).") } .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift index cf9cada592..71c284e9ab 100644 --- a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift @@ -19,7 +19,7 @@ struct MarkdownHelp: View { mdFormat("_italic_", Text("italic").italic()) mdFormat("~strike~", Text("strike").strikethrough()) mdFormat("`a + b`", Text("`a + b`").font(.body.monospaced())) - mdFormat("!1 colored!", Text("colored").foregroundColor(.red) + Text(" (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(")")) + mdFormat("!1 colored!", Text("colored").foregroundColor(.red) + Text(verbatim: " (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(verbatim: ")")) ( mdFormat("#secret#", Text("secret") .foregroundColor(.clear) @@ -39,7 +39,7 @@ private func mdFormat(_ format: LocalizedStringKey, _ example: Text) -> some Vie } private func color(_ s: String, _ c: Color) -> Text { - Text(s).foregroundColor(c) + Text(", ") + Text(s).foregroundColor(c) + Text(verbatim: ", ") } struct MarkdownHelp_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift deleted file mode 100644 index 155a3956be..0000000000 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// NetworkServersView.swift -// SimpleX (iOS) -// -// Created by Evgeny on 02/08/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -private enum NetworkAlert: Identifiable { - case error(err: String) - - var id: String { - switch self { - case let .error(err): return "error \(err)" - } - } -} - -struct NetworkAndServers: View { - @EnvironmentObject var m: ChatModel - @EnvironmentObject var theme: AppTheme - - var body: some View { - VStack { - List { - Section { - NavigationLink { - ProtocolServersView(serverProtocol: .smp) - .navigationTitle("Your SMP servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Message servers") - } - - NavigationLink { - ProtocolServersView(serverProtocol: .xftp) - .navigationTitle("Your XFTP servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Media & file servers") - } - - NavigationLink { - AdvancedNetworkSettings() - .navigationTitle("Advanced settings") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Advanced network settings") - } - } header: { - Text("Messages & files") - .foregroundColor(theme.colors.secondary) - } - - Section(header: Text("Calls").foregroundColor(theme.colors.secondary)) { - NavigationLink { - RTCServers() - .navigationTitle("Your ICE servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("WebRTC ICE servers") - } - } - - Section(header: Text("Network connection").foregroundColor(theme.colors.secondary)) { - HStack { - Text(m.networkInfo.networkType.text) - Spacer() - Image(systemName: "circle.fill").foregroundColor(m.networkInfo.online ? .green : .red) - } - } - } - } - } -} - -struct NetworkServersView_Previews: PreviewProvider { - static var previews: some View { - NetworkAndServers() - } -} diff --git a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift similarity index 67% rename from apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift index 99c0a588eb..fa698f8b7c 100644 --- a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift @@ -36,6 +36,10 @@ struct AdvancedNetworkSettings: View { @State private var showSettingsAlert: NetworkSettingsAlert? @State private var onionHosts: OnionHosts = .no @State private var showSaveDialog = false + @State private var netProxy = networkProxyDefault.get() + @State private var currentNetProxy = networkProxyDefault.get() + @State private var useNetProxy = false + @State private var netProxyAuth = false var body: some View { VStack { @@ -102,6 +106,76 @@ struct AdvancedNetworkSettings: View { .foregroundColor(theme.colors.secondary) } + Section { + Toggle("Use SOCKS proxy", isOn: $useNetProxy) + Group { + TextField("IP address", text: $netProxy.host) + TextField( + "Port", + text: Binding( + get: { netProxy.port > 0 ? "\(netProxy.port)" : "" }, + set: { s in + netProxy.port = if let port = Int(s), port > 0 { + port + } else { + 0 + } + } + ) + ) + Toggle("Proxy requires password", isOn: $netProxyAuth) + if netProxyAuth { + TextField("Username", text: $netProxy.username) + PassphraseField( + key: $netProxy.password, + placeholder: "Password", + valid: NetworkProxy.validCredential(netProxy.password) + ) + } + } + .if(!useNetProxy) { $0.foregroundColor(theme.colors.secondary) } + .disabled(!useNetProxy) + } header: { + HStack { + Text("SOCKS proxy").foregroundColor(theme.colors.secondary) + if useNetProxy && !netProxy.valid { + Spacer() + Image(systemName: "exclamationmark.circle.fill").foregroundColor(.red) + } + } + } footer: { + if netProxyAuth { + Text("Your credentials may be sent unencrypted.") + .foregroundColor(theme.colors.secondary) + } else { + Text("Do not use credentials with proxy.") + .foregroundColor(theme.colors.secondary) + } + } + .onChange(of: useNetProxy) { useNetProxy in + netCfg.socksProxy = useNetProxy && currentNetProxy.valid + ? currentNetProxy.toProxyString() + : nil + netProxy = currentNetProxy + netProxyAuth = netProxy.username != "" || netProxy.password != "" + } + .onChange(of: netProxyAuth) { netProxyAuth in + if netProxyAuth { + netProxy.auth = currentNetProxy.auth + netProxy.username = currentNetProxy.username + netProxy.password = currentNetProxy.password + } else { + netProxy.auth = .username + netProxy.username = "" + netProxy.password = "" + } + } + .onChange(of: netProxy) { netProxy in + netCfg.socksProxy = useNetProxy && netProxy.valid + ? netProxy.toProxyString() + : nil + } + Section { Picker("Use .onion hosts", selection: $onionHosts) { ForEach(OnionHosts.values, id: \.self) { Text($0.text) } @@ -122,15 +196,31 @@ struct AdvancedNetworkSettings: View { if developerTools { Section { Picker("Transport isolation", selection: $netCfg.sessionMode) { - ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) } + let modes = TransportSessionMode.values.contains(netCfg.sessionMode) + ? TransportSessionMode.values + : TransportSessionMode.values + [netCfg.sessionMode] + ForEach(modes, id: \.self) { Text($0.text) } } .frame(height: 36) } footer: { - Text(sessionModeInfo(netCfg.sessionMode)) + sessionModeInfo(netCfg.sessionMode) .foregroundColor(theme.colors.secondary) } } + Section { + Picker("Use web port", selection: $netCfg.smpWebPortServers) { + ForEach(SMPWebPortServers.allCases, id: \.self) { Text($0.text) } + } + .frame(height: 36) + } header: { + Text("TCP port for messaging") + } footer: { + netCfg.smpWebPortServers == .preset + ? Text("Use TCP port 443 for preset servers only.") + : Text("Use TCP port \(netCfg.smpWebPortServers == .all ? "443" : "5223") when no port is specified.") + } + Section("TCP connection") { timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_000000], label: secondsLabel) timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel) @@ -156,19 +246,19 @@ struct AdvancedNetworkSettings: View { Section { Button("Reset to defaults") { - updateNetCfgView(NetCfg.defaults) + updateNetCfgView(NetCfg.defaults, NetworkProxy.def) } .disabled(netCfg == NetCfg.defaults) Button("Set timeouts for proxy/VPN") { - updateNetCfgView(netCfg.withProxyTimeouts) + updateNetCfgView(netCfg.withProxyTimeouts, netProxy) } .disabled(netCfg.hasProxyTimeouts) Button("Save and reconnect") { showSettingsAlert = .update } - .disabled(netCfg == currentNetCfg) + .disabled(netCfg == currentNetCfg || (useNetProxy && !netProxy.valid)) } } } @@ -182,7 +272,8 @@ struct AdvancedNetworkSettings: View { if cfgLoaded { return } cfgLoaded = true currentNetCfg = getNetCfg() - updateNetCfgView(currentNetCfg) + currentNetProxy = networkProxyDefault.get() + updateNetCfgView(currentNetCfg, currentNetProxy) } .alert(item: $showSettingsAlert) { a in switch a { @@ -206,7 +297,7 @@ struct AdvancedNetworkSettings: View { if netCfg == currentNetCfg { dismiss() cfgLoaded = false - } else { + } else if !useNetProxy || netProxy.valid { showSaveDialog = true } }) @@ -221,18 +312,26 @@ struct AdvancedNetworkSettings: View { } } - private func updateNetCfgView(_ cfg: NetCfg) { + private func updateNetCfgView(_ cfg: NetCfg, _ proxy: NetworkProxy) { netCfg = cfg + netProxy = proxy onionHosts = OnionHosts(netCfg: netCfg) enableKeepAlive = netCfg.enableKeepAlive keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults + useNetProxy = netCfg.socksProxy != nil + netProxyAuth = switch netProxy.auth { + case .username: netProxy.username != "" || netProxy.password != "" + case .isolate: false + } } private func saveNetCfg() -> Bool { do { try setNetworkConfig(netCfg) currentNetCfg = netCfg - setNetCfg(netCfg) + setNetCfg(netCfg, networkProxy: useNetProxy ? netProxy : nil) + currentNetProxy = netProxy + networkProxyDefault.set(netProxy) return true } catch let error { let err = responseError(error) @@ -270,10 +369,13 @@ struct AdvancedNetworkSettings: View { } } - private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey { - switch mode { - case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**." - case .entity: return "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." + private func sessionModeInfo(_ mode: TransportSessionMode) -> Text { + let userMode = Text("A separate TCP connection will be used **for each chat profile you have in the app**.") + return switch mode { + case .user: userMode + case .session: userMode + textNewLine + Text("New SOCKS credentials will be used every time you start the app.") + case .server: userMode + textNewLine + Text("New SOCKS credentials will be used for each server.") + case .entity: Text("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.") } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift new file mode 100644 index 0000000000..1e38b7d5ec --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift @@ -0,0 +1,83 @@ +// +// ConditionsWebView.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 26.11.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import WebKit + +struct ConditionsWebView: UIViewRepresentable { + @State var html: String + @EnvironmentObject var theme: AppTheme + @State var pageLoaded = false + + func makeUIView(context: Context) -> WKWebView { + let view = WKWebView() + view.backgroundColor = .clear + view.isOpaque = false + view.navigationDelegate = context.coordinator + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + // just to make sure that even if updateUIView will not be called for any reason, the page + // will be rendered anyway + if !pageLoaded { + loadPage(view) + } + } + return view + } + + func updateUIView(_ view: WKWebView, context: Context) { + loadPage(view) + } + + private func loadPage(_ webView: WKWebView) { + let styles = """ + + """ + let head = "\(styles)" + webView.loadHTMLString(head + html, baseURL: nil) + DispatchQueue.main.async { + pageLoaded = true + } + } + + func makeCoordinator() -> Cordinator { + Cordinator() + } + + class Cordinator: NSObject, WKNavigationDelegate { + func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + guard let url = navigationAction.request.url else { return decisionHandler(.allow) } + + switch navigationAction.navigationType { + case .linkActivated: + decisionHandler(.cancel) + if url.absoluteString.starts(with: "https://simplex.chat/contact#") { + ChatModel.shared.appOpenUrl = url + } else { + UIApplication.shared.open(url) + } + default: + decisionHandler(.allow) + } + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift new file mode 100644 index 0000000000..6f4710396a --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -0,0 +1,480 @@ +// +// NetworkServersView.swift +// SimpleX (iOS) +// +// Created by Evgeny on 02/08/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +private enum NetworkAlert: Identifiable { + case error(err: String) + + var id: String { + switch self { + case let .error(err): return "error \(err)" + } + } +} + +private enum NetworkAndServersSheet: Identifiable { + case showConditions + + var id: String { + switch self { + case .showConditions: return "showConditions" + } + } +} + +struct NetworkAndServers: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var m: ChatModel + @Environment(\.colorScheme) var colorScheme: ColorScheme + @EnvironmentObject var theme: AppTheme + @EnvironmentObject var ss: SaveableSettings + @State private var sheetItem: NetworkAndServersSheet? = nil + @State private var justOpened = true + @State private var showSaveDialog = false + + var body: some View { + VStack { + List { + let conditionsAction = m.conditions.conditionsAction + let anyOperatorEnabled = ss.servers.userServers.contains(where: { $0.operator?.enabled ?? false }) + Section { + ForEach(ss.servers.userServers.enumerated().map { $0 }, id: \.element.id) { idx, userOperatorServers in + if let serverOperator = userOperatorServers.operator { + serverOperatorView(idx, serverOperator) + } else { + EmptyView() + } + } + + if let conditionsAction = conditionsAction, anyOperatorEnabled { + conditionsButton(conditionsAction) + } + } header: { + Text("Preset servers") + .foregroundColor(theme.colors.secondary) + } footer: { + switch conditionsAction { + case let .review(_, deadline, _): + if let deadline = deadline, anyOperatorEnabled { + Text("Conditions will be accepted on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + } + default: + EmptyView() + } + } + + Section { + if let idx = ss.servers.userServers.firstIndex(where: { $0.operator == nil }) { + NavigationLink { + YourServersView( + userServers: $ss.servers.userServers, + serverErrors: $ss.servers.serverErrors, + operatorIndex: idx + ) + .navigationTitle("Your servers") + .modifier(ThemedBackground(grouped: true)) + } label: { + HStack { + Text("Your servers") + + if ss.servers.userServers[idx] != ss.servers.currUserServers[idx] { + Spacer() + unsavedChangesIndicator() + } + } + } + } + + NavigationLink { + AdvancedNetworkSettings() + .navigationTitle("Advanced settings") + .modifier(ThemedBackground(grouped: true)) + } label: { + Text("Advanced network settings") + } + } header: { + Text("Messages & files") + .foregroundColor(theme.colors.secondary) + } + + Section { + Button("Save servers", action: { saveServers($ss.servers.currUserServers, $ss.servers.userServers) }) + .disabled(!serversCanBeSaved(ss.servers.currUserServers, ss.servers.userServers, ss.servers.serverErrors)) + } footer: { + if let errStr = globalServersError(ss.servers.serverErrors) { + ServersErrorView(errStr: errStr) + } else if !ss.servers.serverErrors.isEmpty { + ServersErrorView(errStr: NSLocalizedString("Errors in servers configuration.", comment: "servers error")) + } + } + + Section(header: Text("Calls").foregroundColor(theme.colors.secondary)) { + NavigationLink { + RTCServers() + .navigationTitle("Your ICE servers") + .modifier(ThemedBackground(grouped: true)) + } label: { + Text("WebRTC ICE servers") + } + } + + Section(header: Text("Network connection").foregroundColor(theme.colors.secondary)) { + HStack { + Text(m.networkInfo.networkType.text) + Spacer() + Image(systemName: "circle.fill").foregroundColor(m.networkInfo.online ? .green : .red) + } + } + } + } + .task { + // this condition is needed to prevent re-setting the servers when exiting single server view + if justOpened { + do { + ss.servers.currUserServers = try await getUserServers() + ss.servers.userServers = ss.servers.currUserServers + ss.servers.serverErrors = [] + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error loading servers", comment: "alert title"), + message: responseError(error) + ) + } + } + justOpened = false + } + } + .modifier(BackButton(disabled: Binding.constant(false)) { + if serversCanBeSaved(ss.servers.currUserServers, ss.servers.userServers, ss.servers.serverErrors) { + showSaveDialog = true + } else { + dismiss() + } + }) + .confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) { + Button("Save") { + saveServers($ss.servers.currUserServers, $ss.servers.userServers) + dismiss() + } + Button("Exit without saving") { dismiss() } + } + .sheet(item: $sheetItem) { item in + switch item { + case .showConditions: + UsageConditionsView( + currUserServers: $ss.servers.currUserServers, + userServers: $ss.servers.userServers + ) + .modifier(ThemedBackground(grouped: true)) + } + } + } + + private func serverOperatorView(_ operatorIndex: Int, _ serverOperator: ServerOperator) -> some View { + NavigationLink() { + OperatorView( + currUserServers: $ss.servers.currUserServers, + userServers: $ss.servers.userServers, + serverErrors: $ss.servers.serverErrors, + operatorIndex: operatorIndex, + useOperator: serverOperator.enabled + ) + .navigationBarTitle("\(serverOperator.tradeName) servers") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + HStack { + Image(serverOperator.logo(colorScheme)) + .resizable() + .scaledToFit() + .grayscale(serverOperator.enabled ? 0.0 : 1.0) + .frame(width: 24, height: 24) + Text(serverOperator.tradeName) + .foregroundColor(serverOperator.enabled ? theme.colors.onBackground : theme.colors.secondary) + + if ss.servers.userServers[operatorIndex] != ss.servers.currUserServers[operatorIndex] { + Spacer() + unsavedChangesIndicator() + } + } + } + } + + private func unsavedChangesIndicator() -> some View { + Image(systemName: "pencil") + .foregroundColor(theme.colors.secondary) + .symbolRenderingMode(.monochrome) + .frame(maxWidth: 24, maxHeight: 24, alignment: .center) + } + + private func conditionsButton(_ conditionsAction: UsageConditionsAction) -> some View { + Button { + sheetItem = .showConditions + } label: { + switch conditionsAction { + case .review: + Text("Review conditions") + case .accepted: + Text("Accepted conditions") + } + } + } +} + +struct UsageConditionsView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + switch ChatModel.shared.conditions.conditionsAction { + + case .none: + regularConditionsHeader() + .padding(.top) + .padding(.top) + ConditionsTextView() + .padding(.bottom) + .padding(.bottom) + + case let .review(operators, deadline, _): + HStack { + Text("Updated conditions").font(.largeTitle).bold() + } + .padding(.top) + .padding(.top) + + Text("Conditions will be accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") + ConditionsTextView() + VStack(spacing: 8) { + acceptConditionsButton(operators.map { $0.operatorId }) + if let deadline = deadline { + Text("Conditions will be automatically accepted for enabled operators on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 32) + conditionsDiffButton(.footnote) + } else { + conditionsDiffButton() + .padding(.top) + } + } + .padding(.bottom) + .padding(.bottom) + + + case let .accepted(operators): + regularConditionsHeader() + .padding(.top) + .padding(.top) + Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") + ConditionsTextView() + .padding(.bottom) + .padding(.bottom) + } + } + .padding(.horizontal, 25) + .frame(maxHeight: .infinity) + } + + private func acceptConditionsButton(_ operatorIds: [Int64]) -> some View { + Button { + acceptForOperators(operatorIds) + } label: { + Text("Accept conditions") + } + .buttonStyle(OnboardingButtonStyle()) + } + + func acceptForOperators(_ operatorIds: [Int64]) { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + updateOperatorsConditionsAcceptance($currUserServers, r.serverOperators) + updateOperatorsConditionsAcceptance($userServers, r.serverOperators) + dismiss() + } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } + + @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) { + HStack { + Text("Open changes") + Image(systemName: "arrow.up.right.circle") + } + .font(font) + } + } + } +} + +private func regularConditionsHeader() -> some View { + HStack { + Text("Conditions of use").font(.largeTitle).bold() + Spacer() + conditionsLinkButton() + } +} + +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]>, _ serverErrors: Binding<[UserServersError]>) { + let userServersToValidate = userServers.wrappedValue + Task { + do { + let errs = try await validateServers(userServers: userServersToValidate) + await MainActor.run { + serverErrors.wrappedValue = errs + } + } catch let error { + logger.error("validateServers error: \(responseError(error))") + } + } +} + +func serversCanBeSaved( + _ currUserServers: [UserOperatorServers], + _ userServers: [UserOperatorServers], + _ serverErrors: [UserServersError] +) -> Bool { + return userServers != currUserServers && serverErrors.isEmpty +} + +struct ServersErrorView: View { + @EnvironmentObject var theme: AppTheme + var errStr: String + + var body: some View { + HStack { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + Text(errStr) + .foregroundColor(theme.colors.secondary) + } + } +} + +func globalServersError(_ serverErrors: [UserServersError]) -> String? { + for err in serverErrors { + if let errStr = err.globalError { + return errStr + } + } + return nil +} + +func globalSMPServersError(_ serverErrors: [UserServersError]) -> String? { + for err in serverErrors { + if let errStr = err.globalSMPError { + return errStr + } + } + return nil +} + +func globalXFTPServersError(_ serverErrors: [UserServersError]) -> String? { + for err in serverErrors { + if let errStr = err.globalXFTPError { + return errStr + } + } + return nil +} + +func findDuplicateHosts(_ serverErrors: [UserServersError]) -> Set { + let duplicateHostsList = serverErrors.compactMap { err in + if case let .duplicateServer(_, _, duplicateHost) = err { + return duplicateHost + } else { + return nil + } + } + return Set(duplicateHostsList) +} + +func saveServers(_ currUserServers: Binding<[UserOperatorServers]>, _ userServers: Binding<[UserOperatorServers]>) { + let userServersToSave = userServers.wrappedValue + Task { + do { + try await setUserServers(userServers: userServersToSave) + // Get updated servers to learn new server ids (otherwise it messes up delete of newly added and saved servers) + do { + let updatedServers = try await getUserServers() + let updatedOperators = try await getServerOperators() + await MainActor.run { + ChatModel.shared.conditions = updatedOperators + currUserServers.wrappedValue = updatedServers + userServers.wrappedValue = updatedServers + } + } catch let error { + logger.error("saveServers getUserServers error: \(responseError(error))") + await MainActor.run { + currUserServers.wrappedValue = userServersToSave + } + } + } catch let error { + logger.error("saveServers setUserServers error: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error saving servers", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + +func updateOperatorsConditionsAcceptance(_ usvs: Binding<[UserOperatorServers]>, _ updatedOperators: [ServerOperator]) { + for i in 0.. some View { + VStack { + let serverAddress = parseServerAddress(serverToEdit.server) + let valid = serverAddress?.valid == true + List { + Section { + TextEditor(text: $serverToEdit.server) + .multilineTextAlignment(.leading) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .allowsTightening(true) + .lineLimit(10) + .frame(height: 144) + .padding(-6) + } header: { + HStack { + Text("Your server address") + .foregroundColor(theme.colors.secondary) + if !valid { + Spacer() + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } + } + } + useServerSection(valid) + if valid { + Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { + MutableQRCode(uri: $serverToEdit.server) + .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) + } + } + } + } + } + + private func useServerSection(_ valid: Bool) -> some View { + Section(header: Text("Use server").foregroundColor(theme.colors.secondary)) { + HStack { + Button("Test server") { + testing = true + serverToEdit.tested = nil + Task { + if let f = await testServerConnection(server: $serverToEdit) { + showTestFailure = true + testFailure = f + } + await MainActor.run { testing = false } + } + } + .disabled(!valid || testing) + Spacer() + showTestStatus(server: serverToEdit) + } + Toggle("Use for new connections", isOn: $serverToEdit.enabled) + } + } +} + +func serverProtocolAndOperator(_ server: UserServer, _ userServers: [UserOperatorServers]) -> (ServerProtocol, ServerOperator?)? { + if let serverAddress = parseServerAddress(server.server) { + let serverProtocol = serverAddress.serverProtocol + let hostnames = serverAddress.hostnames + let matchingOperator = userServers.compactMap { $0.operator }.first { op in + op.serverDomains.contains { domain in + hostnames.contains { hostname in + hostname.hasSuffix(domain) + } + } + } + return (serverProtocol, matchingOperator) + } else { + return nil + } +} + +func addServer( + _ server: UserServer, + _ userServers: Binding<[UserOperatorServers]>, + _ serverErrors: Binding<[UserServersError]>, + _ dismiss: DismissAction +) { + if let (serverProtocol, matchingOperator) = serverProtocolAndOperator(server, userServers.wrappedValue) { + if let i = userServers.wrappedValue.firstIndex(where: { $0.operator?.operatorId == matchingOperator?.operatorId }) { + switch serverProtocol { + case .smp: userServers[i].wrappedValue.smpServers.append(server) + case .xftp: userServers[i].wrappedValue.xftpServers.append(server) + } + validateServers_(userServers, serverErrors) + dismiss() + if let op = matchingOperator { + showAlert( + NSLocalizedString("Operator server", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("Server added to operator %@.", comment: "alert message"), op.tradeName) + ) + } + } else { // Shouldn't happen + dismiss() + showAlert(NSLocalizedString("Error adding server", comment: "alert title")) + } + } else { + dismiss() + if server.server.trimmingCharacters(in: .whitespaces) != "" { + showAlert( + NSLocalizedString("Invalid server address!", comment: "alert title"), + message: NSLocalizedString("Check server address and try again.", comment: "alert title") + ) + } + } +} + +#Preview { + NewServerView( + userServers: Binding.constant([UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]) + ) +} diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift new file mode 100644 index 0000000000..afbccc109c --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -0,0 +1,586 @@ +// +// OperatorView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 28.10.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat +import Ink + +struct OperatorView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @Environment(\.colorScheme) var colorScheme: ColorScheme + @EnvironmentObject var theme: AppTheme + @Environment(\.editMode) private var editMode + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var operatorIndex: Int + @State var useOperator: Bool + @State private var useOperatorToggleReset: Bool = false + @State private var showConditionsSheet: Bool = false + @State private var selectedServer: String? = nil + @State private var testing = false + + var body: some View { + operatorView() + .opacity(testing ? 0.4 : 1) + .overlay { + if testing { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .allowsHitTesting(!testing) + } + + private func operatorView() -> some View { + let duplicateHosts = findDuplicateHosts(serverErrors) + return VStack { + List { + Section { + infoViewLink() + useOperatorToggle() + } header: { + Text("Operator") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + switch (userServers[operatorIndex].operator_.conditionsAcceptance) { + case let .accepted(acceptedAt, _): + if let acceptedAt = acceptedAt { + Text("Conditions accepted on: \(conditionsTimestamp(acceptedAt)).") + .foregroundColor(theme.colors.secondary) + } + case let .required(deadline): + if userServers[operatorIndex].operator_.enabled, let deadline = deadline { + Text("Conditions will be accepted on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + } + } + } + } + + if userServers[operatorIndex].operator_.enabled { + if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty { + Section { + Toggle("To receive", isOn: $userServers[operatorIndex].operator_.smpRoles.storage) + .onChange(of: userServers[operatorIndex].operator_.smpRoles.storage) { _ in + validateServers_($userServers, $serverErrors) + } + Toggle("For private routing", isOn: $userServers[operatorIndex].operator_.smpRoles.proxy) + .onChange(of: userServers[operatorIndex].operator_.smpRoles.proxy) { _ in + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Use for messages") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalSMPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } + } + } + + // Preset servers can't be deleted + if !userServers[operatorIndex].smpServers.filter({ $0.preset }).isEmpty { + Section { + ForEach($userServers[operatorIndex].smpServers) { srv in + if srv.wrappedValue.preset { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .smp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + } header: { + Text("Message servers") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalSMPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new connections of your current chat profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } + } + } + + if !userServers[operatorIndex].smpServers.filter({ !$0.preset && !$0.deleted }).isEmpty { + Section { + ForEach($userServers[operatorIndex].smpServers) { srv in + if !srv.wrappedValue.preset && !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .smp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + .onDelete { indexSet in + deleteSMPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Added message servers") + .foregroundColor(theme.colors.secondary) + } + } + + if !userServers[operatorIndex].xftpServers.filter({ !$0.deleted }).isEmpty { + Section { + Toggle("To send", isOn: $userServers[operatorIndex].operator_.xftpRoles.storage) + .onChange(of: userServers[operatorIndex].operator_.xftpRoles.storage) { _ in + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Use for files") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalXFTPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } + } + } + + // Preset servers can't be deleted + if !userServers[operatorIndex].xftpServers.filter({ $0.preset }).isEmpty { + Section { + ForEach($userServers[operatorIndex].xftpServers) { srv in + if srv.wrappedValue.preset { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .xftp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + } header: { + Text("Media & file servers") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalXFTPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new files of your current chat profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } + } + } + + if !userServers[operatorIndex].xftpServers.filter({ !$0.preset && !$0.deleted }).isEmpty { + Section { + ForEach($userServers[operatorIndex].xftpServers) { srv in + if !srv.wrappedValue.preset && !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .xftp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + .onDelete { indexSet in + deleteXFTPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Added media & file servers") + .foregroundColor(theme.colors.secondary) + } + } + + Section { + TestServersButton( + smpServers: $userServers[operatorIndex].smpServers, + xftpServers: $userServers[operatorIndex].xftpServers, + testing: $testing + ) + } + } + } + } + .toolbar { + if ( + !userServers[operatorIndex].smpServers.filter({ !$0.preset && !$0.deleted }).isEmpty || + !userServers[operatorIndex].xftpServers.filter({ !$0.preset && !$0.deleted }).isEmpty + ) { + EditButton() + } + } + .sheet(isPresented: $showConditionsSheet, onDismiss: onUseToggleSheetDismissed) { + SingleOperatorUsageConditionsView( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors, + operatorIndex: operatorIndex + ) + .modifier(ThemedBackground(grouped: true)) + } + } + + private func infoViewLink() -> some View { + NavigationLink() { + OperatorInfoView(serverOperator: userServers[operatorIndex].operator_) + .navigationBarTitle("Network operator") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + Image(userServers[operatorIndex].operator_.largeLogo(colorScheme)) + .resizable() + .scaledToFit() + .grayscale(userServers[operatorIndex].operator_.enabled ? 0.0 : 1.0) + .frame(height: 40) + } + } + + private func useOperatorToggle() -> some View { + Toggle("Use servers", isOn: $useOperator) + .onChange(of: useOperator) { useOperatorToggle in + if useOperatorToggleReset { + useOperatorToggleReset = false + } else if useOperatorToggle { + switch userServers[operatorIndex].operator_.conditionsAcceptance { + case .accepted: + userServers[operatorIndex].operator_.enabled = true + validateServers_($userServers, $serverErrors) + case let .required(deadline): + if deadline == nil { + showConditionsSheet = true + } else { + userServers[operatorIndex].operator_.enabled = true + validateServers_($userServers, $serverErrors) + } + } + } else { + userServers[operatorIndex].operator_.enabled = false + validateServers_($userServers, $serverErrors) + } + } + } + + private func onUseToggleSheetDismissed() { + if useOperator && !userServers[operatorIndex].operator_.conditionsAcceptance.usageAllowed { + useOperatorToggleReset = true + useOperator = false + } + } +} + +func conditionsTimestamp(_ date: Date) -> String { + let localDateFormatter = DateFormatter() + localDateFormatter.dateStyle = .medium + localDateFormatter.timeStyle = .none + return localDateFormatter.string(from: date) +} + +struct OperatorInfoView: View { + @EnvironmentObject var theme: AppTheme + @Environment(\.colorScheme) var colorScheme: ColorScheme + var serverOperator: ServerOperator + + var body: some View { + VStack { + List { + Section { + VStack(alignment: .leading) { + Image(serverOperator.largeLogo(colorScheme)) + .resizable() + .scaledToFit() + .frame(height: 48) + if let legalName = serverOperator.legalName { + Text(legalName) + } + } + } + Section { + VStack(alignment: .leading, spacing: 12) { + ForEach(serverOperator.info.description, id: \.self) { d in + Text(d) + } + } + Link(serverOperator.info.website.absoluteString, destination: serverOperator.info.website) + } + if let selfhost = serverOperator.info.selfhost { + Section { + Link(selfhost.text, destination: selfhost.link) + } + } + } + } + } +} + +struct ConditionsTextView: View { + @State private var conditionsData: (UsageConditions, String?, UsageConditions?)? + @State private var failedToLoad: Bool = false + @State private var conditionsHTML: String? = nil + + let defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md" + + var body: some View { + viewBody() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .task { + do { + let conditions = try await getUsageConditions() + let conditionsText = conditions.1 + let parentLink = "https://github.com/simplex-chat/simplex-chat/blob/\(conditions.0.conditionsCommit)" + let preparedText: String? + if let conditionsText { + let prepared = prepareMarkdown(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines), parentLink) + conditionsHTML = MarkdownParser().html(from: prepared) + preparedText = prepared + } else { + preparedText = nil + } + conditionsData = (conditions.0, preparedText, conditions.2) + } catch let error { + logger.error("ConditionsTextView getUsageConditions error: \(responseError(error))") + failedToLoad = true + } + } + } + + // TODO Diff rendering + @ViewBuilder private func viewBody() -> some View { + if let (usageConditions, _, _) = conditionsData { + if let conditionsHTML { + ConditionsWebView(html: conditionsHTML) + .padding(6) + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) + ) + } else { + let conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/\(usageConditions.conditionsCommit)/PRIVACY.md" + conditionsLinkView(conditionsLink) + } + } else if failedToLoad { + conditionsLinkView(defaultConditionsLink) + } else { + ProgressView() + .scaleEffect(2) + } + } + + 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)!) { + Text(conditionsLink) + .multilineTextAlignment(.leading) + } + } + } + + private func prepareMarkdown(_ text: String, _ parentLink: String) -> String { + let localLinkRegex = try! NSRegularExpression(pattern: "\\[([^\\(]*)\\]\\(#.*\\)") + let h1Regex = try! NSRegularExpression(pattern: "^# ") + var text = localLinkRegex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "$1") + text = h1Regex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "") + return text + .replacingOccurrences(of: "](/", with: "](\(parentLink)/") + .replacingOccurrences(of: "](./", with: "](\(parentLink)/") + } +} + +struct SingleOperatorUsageConditionsView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var operatorIndex: Int + + var body: some View { + viewBody() + } + + @ViewBuilder private func viewBody() -> some View { + let operatorsWithConditionsAccepted = ChatModel.shared.conditions.serverOperators.filter { $0.conditionsAcceptance.conditionsAccepted } + if case .accepted = userServers[operatorIndex].operator_.conditionsAcceptance { + + // In current UI implementation this branch doesn't get shown - as conditions can't be opened from inside operator once accepted + VStack(alignment: .leading, spacing: 20) { + viewHeader() + ConditionsTextView() + } + .padding(.bottom) + .padding(.bottom) + .padding(.horizontal) + .frame(maxHeight: .infinity) + + } else if !operatorsWithConditionsAccepted.isEmpty { + + NavigationView { + VStack(alignment: .leading, spacing: 20) { + viewHeader() + Text("Conditions are already accepted for these operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") + Text("The same conditions will apply to operator **\(userServers[operatorIndex].operator_.legalName_)**.") + conditionsAppliedToOtherOperatorsText() + Spacer() + + acceptConditionsButton() + usageConditionsNavLinkButton() + } + .padding(.bottom) + .padding(.bottom) + .padding(.horizontal) + .frame(maxHeight: .infinity) + } + + } else { + + VStack(alignment: .leading, spacing: 20) { + viewHeader() + Text("To use the servers of **\(userServers[operatorIndex].operator_.legalName_)**, accept conditions of use.") + conditionsAppliedToOtherOperatorsText() + ConditionsTextView() + acceptConditionsButton() + .padding(.bottom) + .padding(.bottom) + } + .padding(.horizontal) + .frame(maxHeight: .infinity) + + } + } + + private func viewHeader() -> some View { + HStack { + Text("Use \(userServers[operatorIndex].operator_.tradeName)").font(.largeTitle).bold() + Spacer() + conditionsLinkButton() + } + .padding(.top) + .padding(.top) + } + + @ViewBuilder private func conditionsAppliedToOtherOperatorsText() -> some View { + let otherOperatorsToApply = ChatModel.shared.conditions.serverOperators.filter { + $0.enabled && + !$0.conditionsAcceptance.conditionsAccepted && + $0.operatorId != userServers[operatorIndex].operator_.operatorId + } + if !otherOperatorsToApply.isEmpty { + Text("These conditions will also apply for: **\(otherOperatorsToApply.map { $0.legalName_ }.joined(separator: ", "))**.") + } + } + + private func acceptConditionsButton() -> some View { + let operatorIds = ChatModel.shared.conditions.serverOperators + .filter { + $0.operatorId == userServers[operatorIndex].operator_.operatorId || // Opened operator + ($0.enabled && !$0.conditionsAcceptance.conditionsAccepted) // Other enabled operators with conditions not accepted + } + .map { $0.operatorId } + return Button { + acceptForOperators(operatorIds, operatorIndex) + } label: { + Text("Accept conditions") + } + .buttonStyle(OnboardingButtonStyle()) + } + + func acceptForOperators(_ operatorIds: [Int64], _ operatorIndexToEnable: Int) { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + updateOperatorsConditionsAcceptance($currUserServers, r.serverOperators) + updateOperatorsConditionsAcceptance($userServers, r.serverOperators) + userServers[operatorIndexToEnable].operator?.enabled = true + validateServers_($userServers, $serverErrors) + dismiss() + } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } + + private func usageConditionsNavLinkButton() -> some View { + NavigationLink("View conditions") { + ConditionsTextView() + .padding() + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } + .modifier(ThemedBackground(grouped: true)) + } + .font(.callout) + .frame(maxWidth: .infinity, alignment: .center) + } +} + +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) { + Label("Open conditions", systemImage: "doc") + } + if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") { + Link(destination: commitUrl) { + Label("Open changes", systemImage: "ellipsis") + } + } + } label: { + Image(systemName: "arrow.up.right.circle") + .resizable() + .scaledToFit() + .frame(width: 20) + .padding(2) + .contentShape(Circle()) + } +} + +#Preview { + OperatorView( + currUserServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), + userServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]), + operatorIndex: 1, + useOperator: ServerOperator.sampleData1.enabled + ) +} diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift similarity index 70% rename from apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift index da29dfac29..13d01874ed 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift @@ -12,15 +12,15 @@ import SimpleXChat struct ProtocolServerView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme - let serverProtocol: ServerProtocol - @Binding var server: ServerCfg - @State var serverToEdit: ServerCfg + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + @Binding var server: UserServer + @State var serverToEdit: UserServer + var backLabel: LocalizedStringKey @State private var showTestFailure = false @State private var testing = false @State private var testFailure: ProtocolTestFailure? - var proto: String { serverProtocol.rawValue.uppercased() } - var body: some View { ZStack { if server.preset { @@ -32,9 +32,33 @@ struct ProtocolServerView: View { ProgressView().scaleEffect(2) } } - .modifier(BackButton(label: "Your \(proto) servers", disabled: Binding.constant(false)) { - server = serverToEdit - dismiss() + .modifier(BackButton(label: backLabel, disabled: Binding.constant(false)) { + if let (serverToEditProtocol, serverToEditOperator) = serverProtocolAndOperator(serverToEdit, userServers), + let (serverProtocol, serverOperator) = serverProtocolAndOperator(server, userServers) { + if serverToEditProtocol != serverProtocol { + dismiss() + showAlert( + NSLocalizedString("Error updating server", comment: "alert title"), + message: NSLocalizedString("Server protocol changed.", comment: "alert title") + ) + } else if serverToEditOperator != serverOperator { + dismiss() + showAlert( + NSLocalizedString("Error updating server", comment: "alert title"), + message: NSLocalizedString("Server operator changed.", comment: "alert title") + ) + } else { + server = serverToEdit + validateServers_($userServers, $serverErrors) + dismiss() + } + } else { + dismiss() + showAlert( + NSLocalizedString("Invalid server address!", comment: "alert title"), + message: NSLocalizedString("Check server address and try again.", comment: "alert title") + ) + } }) .alert(isPresented: $showTestFailure) { Alert( @@ -62,7 +86,7 @@ struct ProtocolServerView: View { private func customServer() -> some View { VStack { let serverAddress = parseServerAddress(serverToEdit.server) - let valid = serverAddress?.valid == true && serverAddress?.serverProtocol == serverProtocol + let valid = serverAddress?.valid == true List { Section { TextEditor(text: $serverToEdit.server) @@ -112,10 +136,7 @@ struct ProtocolServerView: View { Spacer() showTestStatus(server: serverToEdit) } - let useForNewDisabled = serverToEdit.tested != true && !serverToEdit.preset Toggle("Use for new connections", isOn: $serverToEdit.enabled) - .disabled(useForNewDisabled) - .foregroundColor(useForNewDisabled ? theme.colors.secondary : theme.colors.onBackground) } } } @@ -142,7 +163,7 @@ struct BackButton: ViewModifier { } } -@ViewBuilder func showTestStatus(server: ServerCfg) -> some View { +@ViewBuilder func showTestStatus(server: UserServer) -> some View { switch server.tested { case .some(true): Image(systemName: "checkmark") @@ -155,7 +176,7 @@ struct BackButton: ViewModifier { } } -func testServerConnection(server: Binding) async -> ProtocolTestFailure? { +func testServerConnection(server: Binding) async -> ProtocolTestFailure? { do { let r = try await testProtoServer(server: server.wrappedValue.server) switch r { @@ -178,9 +199,11 @@ func testServerConnection(server: Binding) async -> ProtocolTestFailu struct ProtocolServerView_Previews: PreviewProvider { static var previews: some View { ProtocolServerView( - serverProtocol: .smp, - server: Binding.constant(ServerCfg.sampleData.custom), - serverToEdit: ServerCfg.sampleData.custom + userServers: Binding.constant([UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]), + server: Binding.constant(UserServer.sampleData.custom), + serverToEdit: UserServer.sampleData.custom, + backLabel: "Your SMP servers" ) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift new file mode 100644 index 0000000000..b9737914ec --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -0,0 +1,359 @@ +// +// ProtocolServersView.swift +// SimpleX (iOS) +// +// Created by Evgeny on 15/11/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +private let howToUrl = URL(string: "https://simplex.chat/docs/server.html")! + +struct YourServersView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject private var m: ChatModel + @EnvironmentObject var theme: AppTheme + @Environment(\.editMode) private var editMode + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var operatorIndex: Int + @State private var selectedServer: String? = nil + @State private var showAddServer = false + @State private var newServerNavLinkActive = false + @State private var showScanProtoServer = false + @State private var testing = false + + var body: some View { + yourServersView() + .opacity(testing ? 0.4 : 1) + .overlay { + if testing { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .allowsHitTesting(!testing) + } + + private func yourServersView() -> some View { + let duplicateHosts = findDuplicateHosts(serverErrors) + return List { + if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty { + Section { + ForEach($userServers[operatorIndex].smpServers) { srv in + if !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .smp, + backLabel: "Your servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + .onDelete { indexSet in + deleteSMPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Message servers") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalSMPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new connections of your current chat profile **\(m.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } + } + } + + if !userServers[operatorIndex].xftpServers.filter({ !$0.deleted }).isEmpty { + Section { + ForEach($userServers[operatorIndex].xftpServers) { srv in + if !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .xftp, + backLabel: "Your servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + .onDelete { indexSet in + deleteXFTPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Media & file servers") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalXFTPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new files of your current chat profile **\(m.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } + } + } + + Section { + ZStack { + Button("Add server") { + showAddServer = true + } + + NavigationLink(isActive: $newServerNavLinkActive) { + newServerDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } footer: { + if let errStr = globalServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } + } + + Section { + TestServersButton( + smpServers: $userServers[operatorIndex].smpServers, + xftpServers: $userServers[operatorIndex].xftpServers, + testing: $testing + ) + howToButton() + } + } + .toolbar { + if ( + !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty || + !userServers[operatorIndex].xftpServers.filter({ !$0.deleted }).isEmpty + ) { + EditButton() + } + } + .confirmationDialog("Add server", isPresented: $showAddServer, titleVisibility: .hidden) { + Button("Enter server manually") { newServerNavLinkActive = true } + Button("Scan server QR code") { showScanProtoServer = true } + } + .sheet(isPresented: $showScanProtoServer) { + ScanProtocolServer( + userServers: $userServers, + serverErrors: $serverErrors + ) + .modifier(ThemedBackground(grouped: true)) + } + } + + private func newServerDestinationView() -> some View { + NewServerView( + userServers: $userServers, + serverErrors: $serverErrors + ) + .navigationTitle("New server") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + + func howToButton() -> some View { + Button { + DispatchQueue.main.async { + UIApplication.shared.open(howToUrl) + } + } label: { + HStack { + Text("How to use your servers") + Image(systemName: "arrow.up.right.circle") + } + } + } +} + +struct ProtocolServerViewLink: View { + @EnvironmentObject var theme: AppTheme + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var duplicateHosts: Set + @Binding var server: UserServer + var serverProtocol: ServerProtocol + var backLabel: LocalizedStringKey + @Binding var selectedServer: String? + + var body: some View { + let proto = serverProtocol.rawValue.uppercased() + + NavigationLink(tag: server.id, selection: $selectedServer) { + ProtocolServerView( + userServers: $userServers, + serverErrors: $serverErrors, + server: $server, + serverToEdit: server, + backLabel: backLabel + ) + .navigationBarTitle("\(proto) server") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + let address = parseServerAddress(server.server) + HStack { + Group { + if let address = address { + if !address.valid || address.serverProtocol != serverProtocol { + invalidServer() + } else if address.hostnames.contains(where: duplicateHosts.contains) { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } else if !server.enabled { + Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary) + } else { + showTestStatus(server: server) + } + } else { + invalidServer() + } + } + .frame(width: 16, alignment: .center) + .padding(.trailing, 4) + + let v = Text(address?.hostnames.first ?? server.server).lineLimit(1) + if server.enabled { + v + } else { + v.foregroundColor(theme.colors.secondary) + } + } + } + } + + private func invalidServer() -> some View { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } +} + +func deleteSMPServer( + _ userServers: Binding<[UserOperatorServers]>, + _ operatorServersIndex: Int, + _ serverIndexSet: IndexSet +) { + if let idx = serverIndexSet.first { + let server = userServers[operatorServersIndex].wrappedValue.smpServers[idx] + if server.serverId == nil { + userServers[operatorServersIndex].wrappedValue.smpServers.remove(at: idx) + } else { + var updatedServer = server + updatedServer.deleted = true + userServers[operatorServersIndex].wrappedValue.smpServers[idx] = updatedServer + } + } +} + +func deleteXFTPServer( + _ userServers: Binding<[UserOperatorServers]>, + _ operatorServersIndex: Int, + _ serverIndexSet: IndexSet +) { + if let idx = serverIndexSet.first { + let server = userServers[operatorServersIndex].wrappedValue.xftpServers[idx] + if server.serverId == nil { + userServers[operatorServersIndex].wrappedValue.xftpServers.remove(at: idx) + } else { + var updatedServer = server + updatedServer.deleted = true + userServers[operatorServersIndex].wrappedValue.xftpServers[idx] = updatedServer + } + } +} + +struct TestServersButton: View { + @Binding var smpServers: [UserServer] + @Binding var xftpServers: [UserServer] + @Binding var testing: Bool + + var body: some View { + Button("Test servers", action: testServers) + .disabled(testing || allServersDisabled) + } + + private var allServersDisabled: Bool { + smpServers.allSatisfy { !$0.enabled } && xftpServers.allSatisfy { !$0.enabled } + } + + private func testServers() { + resetTestStatus() + testing = true + Task { + let fs = await runServersTest() + await MainActor.run { + testing = false + if !fs.isEmpty { + let msg = fs.map { (srv, f) in + "\(srv): \(f.localizedDescription)" + }.joined(separator: "\n") + showAlert( + NSLocalizedString("Tests failed!", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("Some servers failed the test:\n%@", comment: "alert message"), msg) + ) + } + } + } + } + + private func resetTestStatus() { + for i in 0.. [String: ProtocolTestFailure] { + var fs: [String: ProtocolTestFailure] = [:] + for i in 0..) { switch resp { case let .success(r): - if parseServerAddress(r.string) != nil { - servers.append(ServerCfg(server: r.string, preset: false, tested: nil, enabled: false)) - dismiss() - } else { - showAddressError = true - } + var server: UserServer = .empty + server.server = r.string + addServer(server, $userServers, $serverErrors, dismiss) case let .failure(e): logger.error("ScanProtocolServer.processQRCode QR code error: \(e.localizedDescription)") dismiss() @@ -54,6 +45,9 @@ struct ScanProtocolServer: View { struct ScanProtocolServer_Previews: PreviewProvider { static var previews: some View { - ScanProtocolServer(servers: Binding.constant([])) + ScanProtocolServer( + userServers: Binding.constant([UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]) + ) } } diff --git a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift index b9c92c9919..c4d0588987 100644 --- a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift +++ b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift @@ -13,7 +13,7 @@ struct NotificationsView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @State private var notificationMode: NotificationsMode = ChatModel.shared.notificationMode - @State private var showAlert: NotificationAlert? + @State private var ntfAlert: NotificationAlert? @State private var legacyDatabase = dbContainerGroupDefault.get() == .documents @State private var testing = false @State private var testedSuccess: Bool? = nil @@ -25,7 +25,7 @@ struct NotificationsView: View { ProgressView().scaleEffect(2) } } - .alert(item: $showAlert) { alert in + .alert(item: $ntfAlert) { alert in if let token = m.deviceToken { return notificationAlert(alert, token) } else { @@ -41,7 +41,7 @@ struct NotificationsView: View { List { Section { SelectionListView(list: NotificationsMode.values, selection: $notificationMode) { mode in - showAlert = .setMode(mode: mode) + ntfAlert = .setMode(mode: mode) } } footer: { VStack(alignment: .leading) { @@ -95,7 +95,7 @@ struct NotificationsView: View { if let server = m.notificationServer { smpServers("Push server", [server], theme.colors.secondary) - testServerButton(server) + testTokenButton(server) } } header: { Text("Push notifications") @@ -163,7 +163,7 @@ struct NotificationsView: View { await MainActor.run { let err = responseError(error) logger.error("apiDeleteToken error: \(err)") - showAlert = .error(title: "Error deleting token", error: err) + ntfAlert = .error(title: "Error deleting token", error: err) } } default: @@ -181,19 +181,19 @@ struct NotificationsView: View { await MainActor.run { let err = responseError(error) logger.error("apiRegisterToken error: \(err)") - showAlert = .error(title: "Error enabling notifications", error: err) + ntfAlert = .error(title: "Error enabling notifications", error: err) } } } } } - private func testServerButton(_ server: String) -> some View { + private func testTokenButton(_ server: String) -> some View { HStack { - Button("Test server") { + Button("Test notifications") { testing = true Task { - await testServer(server) + await testServerAndToken(server) await MainActor.run { testing = false } } } @@ -215,31 +215,78 @@ struct NotificationsView: View { } } - private func testServer(_ server: String) async { + private func testServerAndToken(_ server: String) async { do { let r = try await testProtoServer(server: server) switch r { case .success: - await MainActor.run { - testedSuccess = true + if let token = m.deviceToken { + do { + let status = try await apiCheckToken(token: token) + await MainActor.run { + m.tokenStatus = status + testedSuccess = status.workingToken + if status.workingToken { + showAlert( + NSLocalizedString("Notifications status", comment: "alert title"), + message: tokenStatusInfo(status, register: false) + ) + } else { + showAlert( + title: NSLocalizedString("Notifications error", comment: "alert title"), + message: tokenStatusInfo(status, register: true), + buttonTitle: "Register", + buttonAction: { + reRegisterToken(token: token) + testedSuccess = nil + }, + cancelButton: true + ) + } + } + } catch let error { + await MainActor.run { + let err = responseError(error) + logger.error("apiCheckToken \(err)") + ntfAlert = .error(title: "Error checking token status", error: err) + } + } + } else { + await MainActor.run { + showAlert( + NSLocalizedString("No token!", comment: "alert title") + ) + } } case let .failure(f): await MainActor.run { - showAlert = .testFailure(testFailure: f) + ntfAlert = .testFailure(testFailure: f) testedSuccess = false } } } catch let error { - logger.error("testServerConnection \(responseError(error))") + await MainActor.run { + let err = responseError(error) + logger.error("testServerConnection \(err)") + ntfAlert = .error(title: "Error testing server connection", error: err) + } } } } func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey { switch mode { - case .off: return "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." - case .periodic: return "**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." - case .instant: return "**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." + case .off: return "**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." + case .periodic: return "**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." + case .instant: return "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." + } +} + +func ntfModeShortDescription(_ mode: NotificationsMode) -> LocalizedStringKey { + switch mode { + case .off: return "Check messages when allowed." + case .periodic: return "Check messages every 20 min." + case .instant: return "E2E encrypted notifications." } } diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index 0c10da2103..bd8171623a 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -32,6 +32,17 @@ struct PreferencesView: View { .disabled(currentPreferences == preferences) } } + .onDisappear { + if currentPreferences != preferences { + showAlert( + title: NSLocalizedString("Your chat preferences", comment: "alert title"), + message: NSLocalizedString("Chat preferences were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: savePreferences, + cancelButton: true + ) + } + } } private func featureSection(_ feature: ChatFeature, _ allowFeature: Binding) -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 62aad348a7..eba7f8066a 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -19,6 +19,8 @@ struct PrivacySettings: View { @AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true @AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true @State private var simplexLinkMode = privacySimplexLinkModeDefault.get() + @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false + @AppStorage(DEFAULT_PRIVACY_SHORT_LINKS) private var shortSimplexLinks = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @State private var currentLAMode = privacyLocalAuthModeDefault.get() @@ -99,6 +101,11 @@ struct PrivacySettings: View { .onChange(of: simplexLinkMode) { mode in privacySimplexLinkModeDefault.set(mode) } + if developerTools { + settingsRow("link.badge.plus", color: theme.colors.secondary) { + Toggle("Use short links (BETA)", isOn: $shortSimplexLinks) + } + } } header: { Text("Chats") .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift deleted file mode 100644 index 0fb37d5c49..0000000000 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift +++ /dev/null @@ -1,359 +0,0 @@ -// -// ProtocolServersView.swift -// SimpleX (iOS) -// -// Created by Evgeny on 15/11/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -private let howToUrl = URL(string: "https://simplex.chat/docs/server.html")! - -struct ProtocolServersView: View { - @Environment(\.dismiss) var dismiss: DismissAction - @EnvironmentObject private var m: ChatModel - @EnvironmentObject var theme: AppTheme - @Environment(\.editMode) private var editMode - let serverProtocol: ServerProtocol - @State private var currServers: [ServerCfg] = [] - @State private var presetServers: [ServerCfg] = [] - @State private var configuredServers: [ServerCfg] = [] - @State private var otherServers: [ServerCfg] = [] - @State private var selectedServer: String? = nil - @State private var showAddServer = false - @State private var showScanProtoServer = false - @State private var justOpened = true - @State private var testing = false - @State private var alert: ServerAlert? = nil - @State private var showSaveDialog = false - - var proto: String { serverProtocol.rawValue.uppercased() } - - var body: some View { - ZStack { - protocolServersView() - if testing { - ProgressView().scaleEffect(2) - } - } - } - - enum ServerAlert: Identifiable { - case testsFailed(failures: [String: ProtocolTestFailure]) - case error(title: LocalizedStringKey, error: LocalizedStringKey = "") - - var id: String { - switch self { - case .testsFailed: return "testsFailed" - case let .error(title, _): return "error \(title)" - } - } - } - - private func protocolServersView() -> some View { - List { - if !configuredServers.isEmpty { - Section { - ForEach($configuredServers) { srv in - protocolServerView(srv) - } - .onMove { indexSet, offset in - configuredServers.move(fromOffsets: indexSet, toOffset: offset) - } - .onDelete { indexSet in - configuredServers.remove(atOffsets: indexSet) - } - } header: { - Text("Configured \(proto) servers") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("The servers for new connections of your current chat profile **\(m.currentUser?.displayName ?? "")**.") - .foregroundColor(theme.colors.secondary) - .lineLimit(10) - } - } - - if !otherServers.isEmpty { - Section { - ForEach($otherServers) { srv in - protocolServerView(srv) - } - .onMove { indexSet, offset in - otherServers.move(fromOffsets: indexSet, toOffset: offset) - } - .onDelete { indexSet in - otherServers.remove(atOffsets: indexSet) - } - } header: { - Text("Other \(proto) servers") - .foregroundColor(theme.colors.secondary) - } - } - - Section { - Button("Add server") { - showAddServer = true - } - } - - Section { - Button("Reset") { partitionServers(currServers) } - .disabled(Set(allServers) == Set(currServers) || testing) - Button("Test servers", action: testServers) - .disabled(testing || allServersDisabled) - Button("Save servers", action: saveServers) - .disabled(saveDisabled) - howToButton() - } - } - .toolbar { EditButton() } - .confirmationDialog("Add server", isPresented: $showAddServer, titleVisibility: .hidden) { - Button("Enter server manually") { - otherServers.append(ServerCfg.empty) - selectedServer = allServers.last?.id - } - Button("Scan server QR code") { showScanProtoServer = true } - Button("Add preset servers", action: addAllPresets) - .disabled(hasAllPresets()) - } - .sheet(isPresented: $showScanProtoServer) { - ScanProtocolServer(servers: $otherServers) - .modifier(ThemedBackground(grouped: true)) - } - .modifier(BackButton(disabled: Binding.constant(false)) { - if saveDisabled { - dismiss() - justOpened = false - } else { - showSaveDialog = true - } - }) - .confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) { - Button("Save") { - saveServers() - dismiss() - justOpened = false - } - Button("Exit without saving") { dismiss() } - } - .alert(item: $alert) { a in - switch a { - case let .testsFailed(fs): - let msg = fs.map { (srv, f) in - "\(srv): \(f.localizedDescription)" - }.joined(separator: "\n") - return Alert( - title: Text("Tests failed!"), - message: Text("Some servers failed the test:\n" + msg) - ) - case .error: - return Alert( - title: Text("Error") - ) - } - } - .onAppear { - // this condition is needed to prevent re-setting the servers when exiting single server view - if justOpened { - do { - let r = try getUserProtoServers(serverProtocol) - currServers = r.protoServers - presetServers = r.presetServers - partitionServers(currServers) - } catch let error { - alert = .error( - title: "Error loading \(proto) servers", - error: "Error: \(responseError(error))" - ) - } - justOpened = false - } else { - partitionServers(allServers) - } - } - } - - private func partitionServers(_ servers: [ServerCfg]) { - configuredServers = servers.filter { $0.preset || $0.enabled } - otherServers = servers.filter { !($0.preset || $0.enabled) } - } - - private var allServers: [ServerCfg] { - configuredServers + otherServers - } - - private var saveDisabled: Bool { - allServers.isEmpty || - Set(allServers) == Set(currServers) || - testing || - !allServers.allSatisfy { srv in - if let address = parseServerAddress(srv.server) { - return uniqueAddress(srv, address) - } - return false - } || - allServersDisabled - } - - private var allServersDisabled: Bool { - allServers.allSatisfy { !$0.enabled } - } - - private func protocolServerView(_ server: Binding) -> some View { - let srv = server.wrappedValue - return NavigationLink(tag: srv.id, selection: $selectedServer) { - ProtocolServerView( - serverProtocol: serverProtocol, - server: server, - serverToEdit: srv - ) - .navigationBarTitle(srv.preset ? "Preset server" : "Your server") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - let address = parseServerAddress(srv.server) - HStack { - Group { - if let address = address { - if !address.valid || address.serverProtocol != serverProtocol { - invalidServer() - } else if !uniqueAddress(srv, address) { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } else if !srv.enabled { - Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary) - } else { - showTestStatus(server: srv) - } - } else { - invalidServer() - } - } - .frame(width: 16, alignment: .center) - .padding(.trailing, 4) - - let v = Text(address?.hostnames.first ?? srv.server).lineLimit(1) - if srv.enabled { - v - } else { - v.foregroundColor(theme.colors.secondary) - } - } - } - } - - func howToButton() -> some View { - Button { - DispatchQueue.main.async { - UIApplication.shared.open(howToUrl) - } - } label: { - HStack { - Text("How to use your servers") - Image(systemName: "arrow.up.right.circle") - } - } - } - - private func invalidServer() -> some View { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } - - private func uniqueAddress(_ s: ServerCfg, _ address: ServerAddress) -> Bool { - allServers.allSatisfy { srv in - address.hostnames.allSatisfy { host in - srv.id == s.id || !srv.server.contains(host) - } - } - } - - private func hasAllPresets() -> Bool { - presetServers.allSatisfy { hasPreset($0) } - } - - private func addAllPresets() { - for srv in presetServers { - if !hasPreset(srv) { - configuredServers.append(srv) - } - } - } - - private func hasPreset(_ srv: ServerCfg) -> Bool { - allServers.contains(where: { $0.server == srv.server }) - } - - private func testServers() { - resetTestStatus() - testing = true - Task { - let fs = await runServersTest() - await MainActor.run { - testing = false - if !fs.isEmpty { - alert = .testsFailed(failures: fs) - } - } - } - } - - private func resetTestStatus() { - for i in 0.. [String: ProtocolTestFailure] { - var fs: [String: ProtocolTestFailure] = [:] - for i in 0..(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME_IDS, withDefault: [:] ) @@ -245,13 +261,14 @@ public class CodableDefault { } } +let networkProxyDefault: CodableDefault = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_NETWORK_PROXY, withDefault: NetworkProxy.def) struct SettingsView: View { @Environment(\.colorScheme) var colorScheme + @Environment(\.dismiss) var dismiss @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var sceneDelegate: SceneDelegate @EnvironmentObject var theme: AppTheme - @Binding var showSettings: Bool @State private var showProgress: Bool = false var body: some View { @@ -260,203 +277,158 @@ struct SettingsView: View { if showProgress { progressView() } - if let la = chatModel.laRequest { - LocalAuthView(authRequest: la) - } } } - @ViewBuilder func settingsView() -> some View { - let user = chatModel.currentUser - NavigationView { - List { - Section(header: Text("You").foregroundColor(theme.colors.secondary)) { - if let user = user { - NavigationLink { - UserProfile() - .navigationTitle("Your current profile") - .modifier(ThemedBackground()) - } label: { - ProfilePreview(profileOf: user) - .padding(.leading, -8) - } - } - - NavigationLink { - UserProfilesView(showSettings: $showSettings) - } label: { - settingsRow("person.crop.rectangle.stack", color: theme.colors.secondary) { Text("Your chat profiles") } - } - - - if let user = user { - NavigationLink { - UserAddressView(shareViaProfile: user.addressShared) - .navigationTitle("SimpleX address") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("qrcode", color: theme.colors.secondary) { Text("Your SimpleX address") } - } - - NavigationLink { - PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences) - .navigationTitle("Your preferences") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("switch.2", color: theme.colors.secondary) { Text("Chat preferences") } - } - } - - NavigationLink { - ConnectDesktopView(viaSettings: true) - } label: { - settingsRow("desktopcomputer", color: theme.colors.secondary) { Text("Use from desktop") } - } - - NavigationLink { - MigrateFromDevice(showSettings: $showSettings, showProgressOnSettings: $showProgress) - .navigationTitle("Migrate device") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } + func settingsView() -> some View { + List { + let user = chatModel.currentUser + Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { + NavigationLink { + NotificationsView() + .navigationTitle("Notifications") + .modifier(ThemedBackground(grouped: true)) + } label: { + HStack { + notificationsIcon() + Text("Notifications") } } .disabled(chatModel.chatRunning != true) - Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { - NavigationLink { - NotificationsView() - .navigationTitle("Notifications") - .modifier(ThemedBackground(grouped: true)) - } label: { - HStack { - notificationsIcon() - Text("Notifications") - } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - NetworkAndServers() - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - CallSettings() - .navigationTitle("Your calls") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - PrivacySettings() - .navigationTitle("Your privacy") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") } - } - .disabled(chatModel.chatRunning != true) - - if UIApplication.shared.supportsAlternateIcons { - NavigationLink { - AppearanceSettings() - .navigationTitle("Appearance") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") } - } - .disabled(chatModel.chatRunning != true) - } - - chatDatabaseRow() + NavigationLink { + NetworkAndServers() + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } } + .disabled(chatModel.chatRunning != true) - Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { - if let user = user { - NavigationLink { - ChatHelp(showSettings: $showSettings) - .navigationTitle("Welcome \(user.displayName)!") - .modifier(ThemedBackground()) - .frame(maxHeight: .infinity, alignment: .top) - } label: { - settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") } - } - } + NavigationLink { + CallSettings() + .navigationTitle("Your calls") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") } + } + .disabled(chatModel.chatRunning != true) + + NavigationLink { + PrivacySettings() + .navigationTitle("Your privacy") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") } + } + .disabled(chatModel.chatRunning != true) + + if UIApplication.shared.supportsAlternateIcons { NavigationLink { - WhatsNewView(viaSettings: true) - .modifier(ThemedBackground()) - .navigationBarTitleDisplayMode(.inline) + AppearanceSettings() + .navigationTitle("Appearance") + .modifier(ThemedBackground(grouped: true)) } label: { - settingsRow("plus", color: theme.colors.secondary) { Text("What's new") } + settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") } } + .disabled(chatModel.chatRunning != true) + } + } + + Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { + chatDatabaseRow() + NavigationLink { + MigrateFromDevice(showProgressOnSettings: $showProgress) + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Migrate device").font(.headline) + } + } + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } + } + } + + Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { + if let user = user { NavigationLink { - SimpleXInfo(onboarding: false) - .navigationBarTitle("", displayMode: .inline) + ChatHelp(dismissSettingsSheet: dismiss) + .navigationTitle("Welcome \(user.displayName)!") .modifier(ThemedBackground()) .frame(maxHeight: .infinity, alignment: .top) } label: { - settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") } - } - settingsRow("number", color: theme.colors.secondary) { - Button("Send questions and ideas") { - showSettings = false - DispatchQueue.main.async { - UIApplication.shared.open(simplexTeamURL) - } - } - } - .disabled(chatModel.chatRunning != true) - settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") } - } - - 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("star", color: theme.colors.secondary) { - Button("Rate the app") { - if let scene = sceneDelegate.windowScene { - SKStoreReviewController.requestReview(in: scene) - } - } - } - 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) + settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") } } } + NavigationLink { + WhatsNewView(viaSettings: true, updatedConditions: false) + .modifier(ThemedBackground()) + .navigationBarTitleDisplayMode(.inline) + } label: { + settingsRow("plus", color: theme.colors.secondary) { Text("What's new") } + } + NavigationLink { + SimpleXInfo(onboarding: false) + .navigationBarTitle("", displayMode: .inline) + .modifier(ThemedBackground()) + .frame(maxHeight: .infinity, alignment: .top) + } label: { + settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") } + } + settingsRow("number", color: theme.colors.secondary) { + Button("Send questions and ideas") { + dismiss() + DispatchQueue.main.async { + UIApplication.shared.open(simplexTeamURL) + } + } + } + .disabled(chatModel.chatRunning != true) + settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") } + } - Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) { - NavigationLink { - DeveloperView() - .navigationTitle("Developer tools") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") } - } - NavigationLink { - VersionView() - .navigationBarTitle("App version") - .modifier(ThemedBackground()) - } label: { - Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") + 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("star", color: theme.colors.secondary) { + Button("Rate the app") { + if let scene = sceneDelegate.windowScene { + SKStoreReviewController.requestReview(in: scene) + } } } + 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) + } + } + + Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) { + NavigationLink { + DeveloperView() + .navigationTitle("Developer tools") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") } + } + NavigationLink { + VersionView() + .navigationBarTitle("App version") + .modifier(ThemedBackground()) + } label: { + Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") + } } - .navigationTitle("Your settings") - .modifier(ThemedBackground(grouped: true)) } + .navigationTitle("Your settings") + .modifier(ThemedBackground(grouped: true)) .onDisappear { chatModel.showingTerminal = false chatModel.terminalItems = [] @@ -465,7 +437,7 @@ struct SettingsView: View { private func chatDatabaseRow() -> some View { NavigationLink { - DatabaseView(showSettings: $showSettings, chatItemTTL: chatModel.chatItemTTL) + DatabaseView(dismissSettingsSheet: dismiss, chatItemTTL: chatModel.chatItemTTL) .navigationTitle("Your chat database") .modifier(ThemedBackground(grouped: true)) } label: { @@ -504,7 +476,11 @@ struct SettingsView: View { case .registered: icon = "bolt.fill" color = theme.colors.secondary - case .invalid: + case .invalid: fallthrough + case .invalidBad: fallthrough + case .invalidTopic: fallthrough + case .invalidExpired: fallthrough + case .invalidUnregistered: icon = "bolt.slash" color = theme.colors.secondary case .confirmed: @@ -543,26 +519,25 @@ struct ProfilePreview: View { HStack { ProfileImage(imageStr: profileOf.image, size: 44, color: color) .padding(.trailing, 6) - .padding(.vertical, 6) - VStack(alignment: .leading) { - Text(profileOf.displayName) - .fontWeight(.bold) - .font(.title2) - if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { - Text(profileOf.fullName) - } - } + profileName(profileOf).lineLimit(1) } } } +func profileName(_ profileOf: NamedChat) -> Text { + var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) + if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { + t = t + Text(verbatim: " (" + profileOf.fullName + ")") +// .font(.callout) + } + return t +} + struct SettingsView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.currentUser = User.sampleData - @State var showSettings = false - - return SettingsView(showSettings: $showSettings) + return SettingsView() .environmentObject(chatModel) } } diff --git a/apps/ios/Shared/Views/UserSettings/StorageView.swift b/apps/ios/Shared/Views/UserSettings/StorageView.swift new file mode 100644 index 0000000000..094c1cb3d6 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/StorageView.swift @@ -0,0 +1,56 @@ +// +// StorageView.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 13.01.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct StorageView: View { + @State var appGroupFiles: [String: Int64] = [:] + @State var documentsFiles: [String: Int64] = [:] + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + directoryView("App group:", appGroupFiles) + if !documentsFiles.isEmpty { + directoryView("Documents:", documentsFiles) + } + } + } + .padding() + .onAppear { + appGroupFiles = traverseFiles(in: getGroupContainerDirectory()) + documentsFiles = traverseFiles(in: getDocumentsDirectory()) + } + } + + @ViewBuilder + private func directoryView(_ name: LocalizedStringKey, _ contents: [String: Int64]) -> some View { + Text(name).font(.headline) + ForEach(Array(contents), id: \.key) { (key, value) in + Text(key).bold() + Text(verbatim: " ") + Text((ByteCountFormatter.string(fromByteCount: value, countStyle: .binary))) + } + } + + private func traverseFiles(in dir: URL) -> [String: Int64] { + var res: [String: Int64] = [:] + let fm = FileManager.default + do { + if let enumerator = fm.enumerator(at: dir, includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey, .fileAllocatedSizeKey]) { + for case let url as URL in enumerator { + let attrs = try url.resourceValues(forKeys: [/*.isDirectoryKey, .fileSizeKey,*/ .fileAllocatedSizeKey]) + let root = String(url.absoluteString.replacingOccurrences(of: dir.absoluteString, with: "").split(separator: "/")[0]) + res[root] = (res[root] ?? 0) + Int64(attrs.fileAllocatedSize ?? 0) + } + } + } catch { + logger.error("Error traversing files: \(error)") + } + return res + } +} diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index 15f6a1c7d7..6c1ea8deb2 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -9,15 +9,94 @@ import SwiftUI struct UserAddressLearnMore: View { + @State var showCreateAddressButton = false + @State private var createAddressLinkActive = false + @State private var createOneTimeLinkActive = false + var body: some View { - List { - VStack(alignment: .leading, spacing: 18) { - Text("You can share your address as a link or QR code - anybody can connect to you.") - Text("You won't lose your contacts if you later delete your address.") - Text("When people request to connect, you can accept or reject it.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).") + VStack { + List { + VStack(alignment: .leading, spacing: 12) { + (Text(Image(systemName: "envelope")).foregroundColor(.secondary) + textSpace + Text("Share address publicly").bold().font(.title2)) + Text("Share SimpleX address on social media.") + Text("You won't lose your contacts if you later delete your address.") + + (Text(Image(systemName: "link.badge.plus")).foregroundColor(.secondary) + textSpace + Text("Share 1-time link with a friend").font(.title2).bold()) + .padding(.top) + Text("1-time link can be used *with one contact only* - share in person or via any messenger.") + Text("You can set connection name, to remember who the link was shared with.") + + if !showCreateAddressButton { + (Text(Image(systemName: "shield")).foregroundColor(.secondary) + textSpace + Text("Connection security").font(.title2).bold()) + .padding(.top) + Text("SimpleX address and 1-time links are safe to share via any messenger.") + Text("To protect against your link being replaced, you can compare contact security codes.") + Text("Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).") + .padding(.top) + } + + } + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - .listRowBackground(Color.clear) + .frame(maxHeight: .infinity, alignment: .top) + + Spacer() + + if showCreateAddressButton { + VStack { + addressCreationButton() + .padding(.bottom) + + createOneTimeLinkButton() + } + .padding() + } + } + .frame(maxHeight: .infinity, alignment: .top) + } + + private func addressCreationButton() -> some View { + ZStack { + Button { + createAddressLinkActive = true + } label: { + Text("Create SimpleX address") + } + .buttonStyle(OnboardingButtonStyle()) + + NavigationLink(isActive: $createAddressLinkActive) { + UserAddressView(autoCreate: true) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func createOneTimeLinkButton() -> some View { + ZStack { + Button { + createOneTimeLinkActive = true + } label: { + Text("Create 1-time link") + .font(.callout) + } + + NavigationLink(isActive: $createOneTimeLinkActive) { + NewChatView(selection: .invite) + .navigationTitle("New chat") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() } } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index fa95c51d36..4813edf96c 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -8,62 +8,40 @@ import SwiftUI import MessageUI -import SimpleXChat +@preconcurrency import SimpleXChat struct UserAddressView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject private var chatModel: ChatModel @EnvironmentObject var theme: AppTheme - @State var viaCreateLinkView = false @State var shareViaProfile = false + @State var autoCreate = false + @State private var showShortLink = true @State private var aas = AutoAcceptState() @State private var savedAAS = AutoAcceptState() - @State private var ignoreShareViaProfileChange = false @State private var showMailView = false @State private var mailViewResult: Result? = nil @State private var alert: UserAddressAlert? - @State private var showSaveDialogue = false @State private var progressIndicator = false - @FocusState private var keyboardVisible: Bool private enum UserAddressAlert: Identifiable { case deleteAddress - case profileAddress(on: Bool) case shareOnCreate case error(title: LocalizedStringKey, error: LocalizedStringKey?) var id: String { switch self { case .deleteAddress: return "deleteAddress" - case let .profileAddress(on): return "profileAddress \(on)" case .shareOnCreate: return "shareOnCreate" case let .error(title, _): return "error \(title)" } } } - + var body: some View { ZStack { - if viaCreateLinkView { - userAddressScrollView() - } else { - userAddressScrollView() - .modifier(BackButton(disabled: Binding.constant(false)) { - if savedAAS == aas { - dismiss() - } else { - keyboardVisible = false - showSaveDialogue = true - } - }) - .confirmationDialog("Save settings?", isPresented: $showSaveDialogue) { - Button("Save auto-accept settings") { - saveAAS() - dismiss() - } - Button("Exit without saving") { dismiss() } - } - } + userAddressView() + if progressIndicator { ZStack { if chatModel.userAddress != nil { @@ -76,22 +54,10 @@ struct UserAddressView: View { } } } - } - - @Namespace private var bottomID - - private func userAddressScrollView() -> some View { - ScrollViewReader { proxy in - userAddressView() - .onChange(of: keyboardVisible) { _ in - if keyboardVisible { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - withAnimation { - proxy.scrollTo(bottomID, anchor: .top) - } - } - } - } + .onAppear { + if chatModel.userAddress == nil, autoCreate { + createAddress() + } } } @@ -103,14 +69,18 @@ struct UserAddressView: View { aas = AutoAcceptState(userAddress: userAddress) savedAAS = aas } - .onChange(of: aas.enable) { _ in - if !aas.enable { aas = AutoAcceptState() } - } } else { Section { createAddressButton() - } footer: { - Text("Create an address to let people connect with you.") + } header: { + Text("For social media") + .foregroundColor(theme.colors.secondary) + } + + Section { + createOneTimeLinkButton() + } header: { + Text("Or to share privately") .foregroundColor(theme.colors.secondary) } @@ -126,8 +96,8 @@ struct UserAddressView: View { title: Text("Delete address?"), message: shareViaProfile - ? Text("All your contacts will remain connected. Profile update will be sent to your contacts.") - : Text("All your contacts will remain connected."), + ? Text("All your contacts will remain connected. Profile update will be sent to your contacts.") + : Text("All your contacts will remain connected."), primaryButton: .destructive(Text("Delete")) { progressIndicator = true Task { @@ -137,7 +107,6 @@ struct UserAddressView: View { chatModel.userAddress = nil chatModel.updateUser(u) if shareViaProfile { - ignoreShareViaProfileChange = true shareViaProfile = false } } @@ -150,37 +119,12 @@ struct UserAddressView: View { } }, secondaryButton: .cancel() ) - case let .profileAddress(on): - if on { - return Alert( - title: Text("Share address with contacts?"), - message: Text("Profile update will be sent to your contacts."), - primaryButton: .default(Text("Share")) { - setProfileAddress(on) - }, secondaryButton: .cancel() { - ignoreShareViaProfileChange = true - shareViaProfile = !on - } - ) - } else { - return Alert( - title: Text("Stop sharing address?"), - message: Text("Profile update will be sent to your contacts."), - primaryButton: .default(Text("Stop sharing")) { - setProfileAddress(on) - }, secondaryButton: .cancel() { - ignoreShareViaProfileChange = true - shareViaProfile = !on - } - ) - } case .shareOnCreate: return Alert( title: Text("Share address with contacts?"), message: Text("Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts."), primaryButton: .default(Text("Share")) { - setProfileAddress(true) - ignoreShareViaProfileChange = true + setProfileAddress($progressIndicator, true) shareViaProfile = true }, secondaryButton: .cancel() ) @@ -192,22 +136,41 @@ struct UserAddressView: View { @ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View { Section { - SimpleXLinkQRCode(uri: userAddress.connReqContact) - .id("simplex-contact-address-qrcode-\(userAddress.connReqContact)") + SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: $showShortLink) + .id("simplex-contact-address-qrcode-\(userAddress.connLinkContact.simplexChatUri(short: showShortLink))") shareQRCodeButton(userAddress) - if MFMailComposeViewController.canSendMail() { - shareViaEmailButton(userAddress) + // if MFMailComposeViewController.canSendMail() { + // shareViaEmailButton(userAddress) + // } + settingsRow("briefcase", color: theme.colors.secondary) { + Toggle("Business address", isOn: $aas.business) + .onChange(of: aas.business) { ba in + if ba { + aas.enable = true + aas.incognito = false + } + saveAAS($aas, $savedAAS) + } } - shareWithContactsButton() - autoAcceptToggle() - learnMoreButton() + addressSettingsButton(userAddress) } header: { - Text("Address") + ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink) + } footer: { + if aas.business { + Text("Add your team members to the conversations.") + .foregroundColor(theme.colors.secondary) + } + } + + Section { + createOneTimeLinkButton() + } header: { + Text("Or to share privately") .foregroundColor(theme.colors.secondary) } - if aas.enable { - autoAcceptSection() + Section { + learnMoreButton() } Section { @@ -216,32 +179,48 @@ struct UserAddressView: View { Text("Your contacts will remain connected.") .foregroundColor(theme.colors.secondary) } - .id(bottomID) } private func createAddressButton() -> some View { Button { - progressIndicator = true - Task { - do { - let connReqContact = try await apiCreateUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = UserContactLink(connReqContact: connReqContact) - alert = .shareOnCreate - progressIndicator = false - } - } catch let error { - logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") - let a = getErrorAlert(error, "Error creating address") - alert = .error(title: a.title, error: a.message) - await MainActor.run { progressIndicator = false } - } - } + createAddress() } label: { Label("Create SimpleX address", systemImage: "qrcode") } } + private func createAddress() { + progressIndicator = true + Task { + do { + let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) + let connLinkContact = try await apiCreateUserAddress(short: short) + DispatchQueue.main.async { + chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact) + alert = .shareOnCreate + progressIndicator = false + } + } catch let error { + logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") + let a = getErrorAlert(error, "Error creating address") + alert = .error(title: a.title, error: a.message) + await MainActor.run { progressIndicator = false } + } + } + } + + private func createOneTimeLinkButton() -> some View { + NavigationLink { + NewChatView(selection: .invite) + .navigationTitle("New chat") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } label: { + Label("Create 1-time link", systemImage: "link.badge.plus") + .foregroundColor(theme.colors.primary) + } + } + private func deleteAddressButton() -> some View { Button(role: .destructive) { alert = .deleteAddress @@ -253,7 +232,7 @@ struct UserAddressView: View { private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { Button { - showShareSheet(items: [simplexChatLink(userAddress.connReqContact)]) + showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: showShortLink))]) } label: { settingsRow("square.and.arrow.up", color: theme.colors.secondary) { Text("Share address") @@ -291,24 +270,162 @@ struct UserAddressView: View { } } - private func autoAcceptToggle() -> some View { - settingsRow("checkmark", color: theme.colors.secondary) { - Toggle("Auto-accept", isOn: $aas.enable) - .onChange(of: aas.enable) { _ in - saveAAS() - } + private func addressSettingsButton(_ userAddress: UserContactLink) -> some View { + NavigationLink { + UserAddressSettingsView(shareViaProfile: $shareViaProfile) + .navigationTitle("Address settings") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } label: { + Text("Address settings") } } private func learnMoreButton() -> some View { NavigationLink { UserAddressLearnMore() - .navigationTitle("SimpleX address") + .navigationTitle("Address or 1-time link?") .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) + .navigationBarTitleDisplayMode(.inline) } label: { settingsRow("info.circle", color: theme.colors.secondary) { - Text("About SimpleX address") + Text("SimpleX address or 1-time link?") + } + } + } +} + +struct ToggleShortLinkHeader: View { + @EnvironmentObject var theme: AppTheme + let text: Text + var link: CreatedConnLink + @Binding var short: Bool + + var body: some View { + if link.connShortLink == nil { + text.foregroundColor(theme.colors.secondary) + } else { + HStack { + text.foregroundColor(theme.colors.secondary) + Spacer() + Text(short ? "Full link" : "Short link") + .textCase(.none) + .foregroundColor(theme.colors.primary) + .onTapGesture { short.toggle() } + } + } + } +} + +private struct AutoAcceptState: Equatable { + var enable = false + var incognito = false + var business = false + var welcomeText = "" + + init(enable: Bool = false, incognito: Bool = false, business: Bool = false, welcomeText: String = "") { + self.enable = enable + self.incognito = incognito + self.business = business + self.welcomeText = welcomeText + } + + init(userAddress: UserContactLink) { + if let aa = userAddress.autoAccept { + enable = true + incognito = aa.acceptIncognito + business = aa.businessAddress + if let msg = aa.autoReply { + welcomeText = msg.text + } else { + welcomeText = "" + } + } else { + enable = false + incognito = false + business = false + welcomeText = "" + } + } + + var autoAccept: AutoAccept? { + if enable { + var autoReply: MsgContent? = nil + let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) + if s != "" { autoReply = .text(s) } + return AutoAccept(businessAddress: business, acceptIncognito: incognito, autoReply: autoReply) + } + return nil + } +} + +private func setProfileAddress(_ progressIndicator: Binding, _ on: Bool) { + progressIndicator.wrappedValue = true + Task { + do { + if let u = try await apiSetProfileAddress(on: on) { + DispatchQueue.main.async { + ChatModel.shared.updateUser(u) + } + } + await MainActor.run { progressIndicator.wrappedValue = false } + } catch let error { + logger.error("apiSetProfileAddress: \(responseError(error))") + await MainActor.run { progressIndicator.wrappedValue = false } + } + } +} + +struct UserAddressSettingsView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + @Binding var shareViaProfile: Bool + @State private var aas = AutoAcceptState() + @State private var savedAAS = AutoAcceptState() + @State private var ignoreShareViaProfileChange = false + @State private var progressIndicator = false + @FocusState private var keyboardVisible: Bool + + var body: some View { + ZStack { + if let userAddress = ChatModel.shared.userAddress { + userAddressSettingsView() + .onAppear { + aas = AutoAcceptState(userAddress: userAddress) + savedAAS = aas + } + .onChange(of: aas.enable) { aasEnabled in + if !aasEnabled { aas = AutoAcceptState() } + } + .onDisappear { + if savedAAS != aas { + showAlert( + title: NSLocalizedString("Auto-accept settings", comment: "alert title"), + message: NSLocalizedString("Settings were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: { saveAAS($aas, $savedAAS) }, + cancelButton: true + ) + } + } + } else { + Text(String("Error opening address settings")) + } + if progressIndicator { + ProgressView().scaleEffect(2) + } + } + } + + private func userAddressSettingsView() -> some View { + List { + Section { + shareWithContactsButton() + autoAcceptToggle().disabled(aas.business) + } + + if aas.enable { + autoAcceptSection() } } } @@ -320,70 +437,70 @@ struct UserAddressView: View { if ignoreShareViaProfileChange { ignoreShareViaProfileChange = false } else { - alert = .profileAddress(on: on) + if on { + showAlert( + NSLocalizedString("Share address with contacts?", comment: "alert title"), + message: NSLocalizedString("Profile update will be sent to your contacts.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default, + handler: { _ in + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ), + UIAlertAction( + title: NSLocalizedString("Share", comment: "alert action"), + style: .default, + handler: { _ in + setProfileAddress($progressIndicator, on) + } + ) + ]} + ) + } else { + showAlert( + NSLocalizedString("Stop sharing address?", comment: "alert title"), + message: NSLocalizedString("Profile update will be sent to your contacts.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default, + handler: { _ in + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ), + UIAlertAction( + title: NSLocalizedString("Stop sharing", comment: "alert action"), + style: .default, + handler: { _ in + setProfileAddress($progressIndicator, on) + } + ) + ]} + ) + } } } } } - private func setProfileAddress(_ on: Bool) { - progressIndicator = true - Task { - do { - if let u = try await apiSetProfileAddress(on: on) { - DispatchQueue.main.async { - chatModel.updateUser(u) - } + private func autoAcceptToggle() -> some View { + settingsRow("checkmark", color: theme.colors.secondary) { + Toggle("Auto-accept", isOn: $aas.enable) + .onChange(of: aas.enable) { _ in + saveAAS($aas, $savedAAS) } - await MainActor.run { progressIndicator = false } - } catch let error { - logger.error("UserAddressView apiSetProfileAddress: \(responseError(error))") - await MainActor.run { progressIndicator = false } - } } } - private struct AutoAcceptState: Equatable { - var enable = false - var incognito = false - var welcomeText = "" - - init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { - self.enable = enable - self.incognito = incognito - self.welcomeText = welcomeText - } - - init(userAddress: UserContactLink) { - if let aa = userAddress.autoAccept { - enable = true - incognito = aa.acceptIncognito - if let msg = aa.autoReply { - welcomeText = msg.text - } else { - welcomeText = "" - } - } else { - enable = false - incognito = false - welcomeText = "" - } - } - - var autoAccept: AutoAccept? { - if enable { - var autoReply: MsgContent? = nil - let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) - if s != "" { autoReply = .text(s) } - return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) - } - return nil - } - } - - @ViewBuilder private func autoAcceptSection() -> some View { + private func autoAcceptSection() -> some View { Section { - acceptIncognitoToggle() + if !aas.business { + acceptIncognitoToggle() + } welcomeMessageEditor() saveAASButton() .disabled(aas == savedAAS) @@ -423,22 +540,24 @@ struct UserAddressView: View { private func saveAASButton() -> some View { Button { keyboardVisible = false - saveAAS() + saveAAS($aas, $savedAAS) } label: { Text("Save") } } +} - private func saveAAS() { - Task { - do { - if let address = try await userAddressAutoAccept(aas.autoAccept) { - chatModel.userAddress = address - savedAAS = aas +private func saveAAS(_ aas: Binding, _ savedAAS: Binding) { + Task { + do { + if let address = try await userAddressAutoAccept(aas.wrappedValue.autoAccept) { + await MainActor.run { + ChatModel.shared.userAddress = address + savedAAS.wrappedValue = aas.wrappedValue } - } catch let error { - logger.error("userAddressAutoAccept error: \(responseError(error))") } + } catch let error { + logger.error("userAddressAutoAccept error: \(responseError(error))") } } } @@ -446,7 +565,9 @@ struct UserAddressView: View { struct UserAddressView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() - chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") + chatModel.userAddress = UserContactLink(connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil)) + + return Group { UserAddressView() .environmentObject(chatModel) diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 198fd495bd..9aa42930bf 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -11,8 +11,11 @@ import SimpleXChat struct UserProfile: View { @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner @State private var profile = Profile(displayName: "", fullName: "") - @State private var editProfile = false + @State private var currentProfileHash: Int? + // Modals @State private var showChooseSource = false @State private var showImagePicker = false @State private var showTakePhoto = false @@ -21,85 +24,86 @@ struct UserProfile: View { @FocusState private var focusDisplayName var body: some View { - let user: User = chatModel.currentUser! - - return VStack(alignment: .leading) { - Text("Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile.") - .padding(.bottom) - - if editProfile { - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { + List { + Group { + if profile.image != nil { + ZStack(alignment: .bottomTrailing) { + ZStack(alignment: .topTrailing) { + profileImageView(profile.image) + .onTapGesture { showChooseSource = true } + overlayButton("multiply", edge: .top) { profile.image = nil } + } + overlayButton("camera", edge: .bottom) { showChooseSource = true } + } + } else { + ZStack(alignment: .center) { profileImageView(profile.image) - if user.image != nil { - Button { - profile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - } + editImageButton { showChooseSource = true } + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.top) + .contentShape(Rectangle()) + + Section { + HStack { + TextField("Enter your name…", text: $profile.displayName) + .focused($focusDisplayName) + if !validDisplayName(profile.displayName) { + Button { + alert = .invalidNameError(validName: mkValidName(profile.displayName)) + } label: { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) } } - - editImageButton { showChooseSource = true } } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - ZStack(alignment: .leading) { - if !validNewProfileName(user) { - Button { - alert = .invalidNameError(validName: mkValidName(profile.displayName)) - } label: { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) - } - profileNameTextEdit("Profile name", $profile.displayName) - .focused($focusDisplayName) - } - .padding(.bottom) - if showFullName(user) { - profileNameTextEdit("Full name (optional)", $profile.fullName) - .padding(.bottom) - } - HStack(spacing: 20) { - Button("Cancel") { editProfile = false } - Button("Save (and notify contacts)") { saveProfile() } - .disabled(!canSaveProfile(user)) - } + if let user = chatModel.currentUser, showFullName(user) { + TextField("Full name (optional)", text: $profile.fullName) } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) - } else { - ZStack(alignment: .center) { - profileImageView(user.image) - .onTapGesture { startEditingImage(user) } + } footer: { + Text("Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.") + } - if user.image == nil { - editImageButton { startEditingImage(user) } - } + Section { + Button(action: getCurrentProfile) { + Text("Reset") } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - profileNameView("Profile name:", user.profile.displayName) - if showFullName(user) { - profileNameView("Full name:", user.profile.fullName) - } - Button("Edit") { - profile = fromLocalProfile(user.profile) - editProfile = true - focusDisplayName = true - } + .disabled(currentProfileHash == profile.hashValue) + Button(action: saveProfile) { + Text("Save (and notify contacts)") } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + .disabled(!canSaveProfile) } } - .padding() - .frame(maxHeight: .infinity, alignment: .top) + // Lifecycle + .onAppear { + getCurrentProfile() + } + .onDisappear { + if canSaveProfile { + showAlert( + title: NSLocalizedString("Save your profile?", comment: "alert title"), + message: NSLocalizedString("Your profile was changed. If you save it, the updated profile will be sent to all your contacts.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save (and notify contacts)", comment: "alert button"), + buttonAction: saveProfile, + cancelButton: true + ) + } + } + .onChange(of: chosenImage) { image in + Task { + let resized: String? = if let image { + await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + nil + } + await MainActor.run { profile.image = resized } + } + } + // Modals .confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true @@ -126,57 +130,48 @@ struct UserProfile: View { } } } - .onChange(of: chosenImage) { image in - if let image = image { - profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) - } else { - profile.image = nil - } - } .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } } - func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding) -> some View { - TextField(label, text: name) - .padding(.leading, 32) - } - - func profileNameView(_ label: LocalizedStringKey, _ name: String) -> some View { - HStack { - Text(label) - Text(name).fontWeight(.bold) - } - .padding(.bottom) - } - - func startEditingImage(_ user: User) { - profile = fromLocalProfile(user.profile) - editProfile = true - showChooseSource = true - } - - private func validNewProfileName(_ user: User) -> Bool { - profile.displayName == user.profile.displayName || validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces)) + private func overlayButton( + _ systemName: String, + edge: Edge.Set, + action: @escaping () -> Void + ) -> some View { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 12) + .foregroundColor(theme.colors.primary) + .padding(6) + .frame(width: 36, height: 36, alignment: .center) + .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) + .clipShape(Circle()) + .contentShape(Circle()) + .padding([.trailing, edge], -12) + .onTapGesture(perform: action) } private func showFullName(_ user: User) -> Bool { user.profile.fullName != "" && user.profile.fullName != user.profile.displayName } - - private func canSaveProfile(_ user: User) -> Bool { - profile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName(user) + + private var canSaveProfile: Bool { + currentProfileHash != profile.hashValue && + profile.displayName.trimmingCharacters(in: .whitespaces) != "" && + validDisplayName(profile.displayName) } - func saveProfile() { + private func saveProfile() { + focusDisplayName = false Task { do { profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) if let (newProfile, _) = try await apiUpdateProfile(profile: profile) { - DispatchQueue.main.async { + await MainActor.run { chatModel.updateCurrentUser(newProfile) - profile = newProfile + getCurrentProfile() } - editProfile = false } else { alert = .duplicateUserError } @@ -185,6 +180,13 @@ struct UserProfile: View { } } } + + private func getCurrentProfile() { + if let user = chatModel.currentUser { + profile = fromLocalProfile(user.profile) + currentProfileHash = profile.hashValue + } + } } func profileImageView(_ imageStr: String?) -> some View { @@ -201,19 +203,3 @@ func editImageButton(action: @escaping () -> Void) -> some View { .frame(width: 48) } } - -struct UserProfile_Previews: PreviewProvider { - static var previews: some View { - let chatModel1 = ChatModel() - chatModel1.currentUser = User.sampleData - let chatModel2 = ChatModel() - chatModel2.currentUser = User.sampleData - chatModel2.currentUser?.profile.image = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAAqACAAQAAAABAAAAgKADAAQAAAABAAAAgAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/+ICNElDQ19QUk9GSUxFAAEBAAACJGFwcGwEAAAAbW50clJHQiBYWVogB+EABwAHAA0AFgAgYWNzcEFQUEwAAAAAQVBQTAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsyhqVgiV/EE04mRPV0eoVggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZGVzYwAAAPwAAABlY3BydAAAAWQAAAAjd3RwdAAAAYgAAAAUclhZWgAAAZwAAAAUZ1hZWgAAAbAAAAAUYlhZWgAAAcQAAAAUclRSQwAAAdgAAAAgY2hhZAAAAfgAAAAsYlRSQwAAAdgAAAAgZ1RSQwAAAdgAAAAgZGVzYwAAAAAAAAALRGlzcGxheSBQMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDE3AABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAACD3wAAPb////+7WFlaIAAAAAAAAEq/AACxNwAACrlYWVogAAAAAAAAKDgAABELAADIuXBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeTAAD9kP//+6L///2jAAAD3AAAwG7/wAARCACAAIADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwABAQEBAQECAQECAwICAgMEAwMDAwQGBAQEBAQGBwYGBgYGBgcHBwcHBwcHCAgICAgICQkJCQkLCwsLCwsLCwsL/9sAQwECAgIDAwMFAwMFCwgGCAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL/90ABAAI/9oADAMBAAIRAxEAPwD4N1TV59SxpunRtBb/APPP/lo+eMsf4R+uKyxNa6Y32a3UTzjoi8Ip9/8AOfYV0tx4d1a8VlsojaWo6uThj+Pb6Cs2CCGyP2LQ4xPIMBpGIVVz7ngV+Ap31P2C1iSDQbnWXRtVYyMT8kSDkZ9B29zXXReD7ZVOkX0QlLgg2ycjBH8ZHXPoOK9O8L6LpljZidWMjyqMzAdc/wB3PJ+p4qjrPiuxs1a38LwLJIn35ScoP94jlm9hxW8ZKJm1fY/Gv4yeA/E37L3xf07xz4GuH0260+7i1bRLpDkwzQOHVfQ+WwAI7r1zmv7fv2Nv2nfCv7YH7PHh346+FwkD6nEYtRs1OTZ6jBhbiA98K/zJnrGynvX8u3x3+G6fFvwXcadcOZNTQebZyN1EgH3QB91W6H657VD/AMEYP2qdQ/Zb/aRuPgN8RpjZeFviJcJabJztWy1tPkgkOeFE3+ok9zGTwtfY5Nj1Vjyt6nzuZ4XlfMj+zamH5TupVYnhhgjsaRyMYNe8eEMC7jxxU+1SMYFQFyaevPWgRqaeuSVFb0SDgAZI/SsLS9w4kxux1HTNdTEAMDvQJst20UitvA4rotMh8ycbuAv6k1Rs3UgcHjrXc6Xb2iTKVIJPQEcZ96qKMW7nWabpNmzRyEE9wOlegtplzFCLiMbEcfKw5/XP51l6ZPK6b2SJsdd64A/Kr0t5fyRsqsPLU5baNo49P0q2I//Q8iuPD17eeTpVy32u2ufls5lAC5P8MmOA2O/Q/XIrHl+GWn+CGN7qyC9ugxkSID92nvz1+pwK/TKb9j34t3Pw/PjXXrpdR165L3F7pkiDz5RISzHzFIUzliXKBQCTgMGwD8P6zompRzR2V2xuLWV9sE7ggo4yPLlBxhgRgE8k8cHivyPPMl9g3iMMrw6r+X/gH6PlmZ+1tRrP3uj7/wDBPnjXdR1rXWDao5jtm4S3h43gf3jwSPyH1rW0Xw9f6uyw2MYSNAAT/Ag/qa9ii+GTWEv2nV8nfztH3m/+t/nirMsVtMPscGIYYuCqjj8fWvmo+9qz227aI5O38NeH/DeJIGE079ZW9fQf/W/Ovyx/ba+C1x/aR+K/h6FoLa5dUvDH8rRzj7kgI+7ux253DPev1yuINKtF3XriOMDlm+83+6O1eNePZoPH2h3ngWC032N7E0UhI7HuPcdQfWvQweJdKakjkxFFTjZn6+f8Eu/2yE/a+/Zss9R8TXCyeMvCpTSfECZ+eSZF/dXWPS5jG4n/AJ6Bx2r9JGbd0r+GX9jD476z/wAE5v20IL3xPM7eGdUZdK8QBeUewmYGO6A7tbviT127171/cfaXdve28d1aSJNFKqukiHcjqwyGUjggggg9xX6Dhq6q01JM+NxVF05tdCyRQCOvakY4GRTFYd66DmN2xk2sK6eE5+YVxlo5EwB4rrLZiTyePWgmSOmsAThCcZPFdxZ5KruJyprgrWQ5G3tXS21+FABzVrYyZ6ZZTTSqCR8vQ4rUudWgW1e3QMrBScj1/D+tcpp1+UXaOn09fWtKP7OAzNjK+tNiP//R/oYjkSW9NgqsWVA7HHyrk4AJ9Tzx6CvjL9qz4M+FrbRrn4q2s0Fjcs6R3ttKdsd+ZCFBUf8APx0xj/WAYOCA1fVF58Y/hbb/AAwPxlXWIH8OCHzhdKc57bAv3vM3fLsxu3cYzX58eGdH8f8A7b/xIHi/xOs2k+DNGkK28AOCgPVQejXMg++/IiU7RyefmI+Z79+qPl++0JpR/wATG7Z9M4WOQfeVv7srdT/snp+NeWa9bfZXez8KxCZQcGVhiJT/AOzH6fnX7K/Fn9mfwzf6N9r+GmnwWV3DF5UlmBiC8iAxtbPAkx0c/e6N/eH5s+IvDcuj2jWcUTJYwsYXDrtktHXgxuvBxngE9Oh9/is6yVUr4nDL3Oq7enl+R9Plmac9qNZ+90ff/gnybLoheT7XrM3nMo5JH8h2HtXJa9/aGoMYbAC0gTqwH7x1H8hXsHiWGDRUboqr/Eeck+nrXj9/d3twWmlzbQHnn77e/tXzaqXXuntuNtz4z/ay+Eul+NPAf9u+H4TLq2kqzEAfNLAeXU/T7w/Ed6/XL/giD+2n/wALr+Ck37Nnjq78zxV8PYkW0Z2+a60VjthbJ5LWzfuW/wBjyz3NfCGuJLLm30tSsT8OT/U1+b1v4w8VfsE/tXeHf2kfhqjz2Vvcl5rdDiO4tZflu7Q+zoSUz0baeq19RkWMUZexk/Q8LNMLzx51uf3yIxPXvTQuTkVw3wz+IfhH4seBNG+JngS7W+0XX7OG/sp1P34ZlDLn0Izhh2YEGu+LAHFfXo+XJ4P9cp6YNdbCWHFcerFSCK6OGcMBk0wOmtZMVswurDNcnHKB7VqxXbDGKaZEoncRXpt4iy8fWlN44XdM5+bGPauWbUAI9p5NeH/E39oTwF8OAdO1W6+06kfuWVuQ0vtvOcIPdiPalOrGC5pOyHToym7RV2f/0nXmiaPrF/ceJvC1hrUnhC11EyFGZsIN2Mtg+QLjy+A5GQcZI6V/QP8ABrWvhd4i+GmnXXwZeI6DAnkxRxgq0LL95JFb5hJnO7dyTz3qt4f8EeCPC3g5Pht4csYItKt4fKNngMpjfOd4PJLckk8k18FeKvBXj79kHxu/xW+ECte+F711XUtNdiVC54VvQj/lnL2+63FfNNqWh7rVtT9JdItdaitpV8QSxyy+a5VowVURE/KDnuB1PQ9a/OD4yfEbwv8AEP4rx6F8JNIfXb4QyQXMlqAwvmQgEBThSkQBUysQpyFBOBjE+NH7WWu/HtrH4QfACxvYpNZHl3bSr5M7kjLQqc/JGo5ml/u8DrX2X+z38A9C+B3hzyQUvNbvVX7dehcA7ekUQ/hiT+Fe/U81m1bVj1Px/wDiX4FXQ4b7WNItJXitXZLq3nU+fpzjqpQ87PQ88eowa+JdanuvP+03JzG3Kk87voP8a/pi+NPwStfiAo8V+GDHaeI7aPYsjj91dxj/AJYzjuOyv1X6V+Mfxk+By6eL7xPodhLE9kzDUNJYfvbSXqWUd4z147cjivjc3ybkviMMtOq7eaPo8tzXmtRrvXo/8z4aaC/1a3drrbDbr6nCgepPc+36V4T8Z/A/h7xz4KvPB8uGmcb4LhhxHKv3WUeh6HPY17TrMuo3dysUA3p0VUGEArCudFt7aH7bqjguOQP6V89SquLUk9T26lNNWZ7L/wAEJv2vNQ8L6xq/7BPxZma3ureafUPDHnHvy93Zg/X9/EO+XA7Cv6fFwRnNfwWftIWHi/wL4u0T9pX4Vu2ma74buobpJY+GEkDBo5CO4B+Vx3U4PFf2VfshftPeFf2tv2e/Dvx18LbYhq0G29tQcm0vovluID/uPkr6oVPev0TLsWq9FT69T43MMN7KpdbM+q1kA+WtuF8qCa5H7SD0qvrnjbw34L0KTxD4qvobCyhBLzTuFUY7DPU+wya7nNJXZwxu3ZHoqyqq5JxXnPxL+Nvw3+EemjUPHmqxWIbPlxcvNIR2WNcsfrjFflz8cf8AgpDJMZ/DvwKgwOVOq3S/rFGf0LV8MaZp/jf4j603ibxTdT3U053PdXRLu+eflB7fkK8PFZ5TheNHV/h/wT2cLlFSfvVNF+J+hnxI/ba8cfEa5fQfhnG+h6e5KCY/NeTD6jIjH0yfcV514W8HX2plrjUiWLEtIWbcSSOS7dST/k1x2g2PhrwdZhpyFbHzEnLk+5/oK6eDxRq2soYdPH2S0xjjh2H9K+erY+pVlzTdz3aWEhSjaCsf/9P+gafwFajxovjGKeVJSqrJEPuOVUoD7ZBGR32ivgn9pz9pHUfGOvP+zb8BIDrGr6kZLO/nhwUXH34UY/LwP9bJ91BxndxXyp41/ab/AGivht4c1D9mf+0La7vrOY6f/asUpe4WP7vlRzEhRnIHmMNyAkcEcfpB+zB+zBo37O/hQ3moBL3xLfxA312gyFA5EEOeRGp79Xb5j2x8wfQHyHZ/CP41fsg6lZ/GHT3tvEVvDC0WqxwIU8uGUqXXnnaCoIlHQj5vlOR+lPwv+Lngv4v+Gk8UeC7oTRBvLnib5ZYJcZKSL1B9D0YcgkU/QfEkXitbuzuLR7S5tGCTwS4bAfO3kcEEA5B/lg1+Yn7Qdtbfsd/E/TPiT8IdShs21jzDc6HIf3TRIQWyB0hYnCE8xt9044Ckr7k7H7AiUEf4V438U/hZa+O0TXNGkWy120XbDcEfJKn/ADxmA+8h7Hqp5HpWN8Efjv4N+OvhFfFHhOTy5otqXlnIR51tKRnaw7g9VccMOnOQPXZ71Yo2mdgiqMsWOAAOufasXoyrXPw++NX7P9zHdX174Q0wWOqW/wC81DSjjMe7J86HHDxtgnC5zzjkEV+Z3iOS20u7PlZupiT+9YYQH/ZWv6hvjRp3grXPAJ8c3t6lldabGZLC/j5be3KxY/jSUgAp+IwRkfzs/tYan4Vi+LM8nhzyo5bq2gnu4Iukd04PmDI6ZGGIHc18hnmW06K+s09LvVefkfRZTjZ1H7Cetlo/8z5d1bQk1m1ng1OMTRXCGOVX+7tbg5+tQf8ABPL9o/xV/wAE9vi/r3gDxhYahrPw18WSrMJbGMzvZXcYwkyxjn5k/dyr1OFI6VqBpJ8LdPiM9gOv0FWFTzJBFbJtzgADliT0H515uAzKphpNxV0z0sVhIVo8sj9rviP/AMFJPhxpuhJ/wqm2n1rUbhcqbmJreKLP95T8zEeg/GvzP8Y/Eb4vftA+Ije+Kb2XUWU/JCDstoAewH3Rj8TXmOi+HrJYTd63MII1OPLB+d8diev4DtXtWjeIrPTNNENtD9mjx8kY+V2H0/hH60YzNK2IdpPTsthYXL6VHWK17s2/C3gHQvDCLqPiKRZ7hei/wKfYdz7mu9/4TGa5lEGjREA8Z7/5+lec2Ntf65KLm+IjhXkZ4UCunt9X0zTONN56gu39K4k2dtlueh6Xpdxcz/a9UfMi84J4X+grv7fxNaaehi0oCWUDDSH7o+leNW99f30fls3l2+eT0z61oDVFgiEOngtgY3Y/kP61pEln/9T74+Ff/BPn4e6R8MnsPieWvfFF+haS+gkbbZM3RIQeHA/jLjMhznAwBufCz42+Mf2bPEsHwM/aNlMmiONmj6+cmIRg4Cuxz+7GQMn5oicNlcGvWf2ffiB418d/Dfwn4tvR9st9StTb3IVVUxSw8NK7E5O4qRgeo46msH9tXx78JfAfwS1CL4oQx30l8ki6XZ5Ama7VTtkQ9UWPIMjdNvynO4A/NHvnqP7Rn7Q/gX9nLwY3iXVGiudR1BS2n2aOA102PvkjpEowWfpjgcmviz9nH9njxT8afFEn7SX7TkJvJL8+bp+mXSfIUP3JJIyPljUf6qI9vmPOK+DfgboFl4V+LfhHxt+1DpWoW/he7iL6bJfRt9mLpgwOwbOYIyd23sSrFdvX+iZ7n7bY+fpkqHzU3RSj50IYZVuDhh34PIqG7bBufnr8Zv2fvF3wa8Vf8L8/ZgQ20sAJ1DR4lLRPF1fbGPvRHGWjHKn5kxjFe8fDD9qX4Q/FL4cXni/V7uHS2sIv+JpYXLgyQE/3RwZEc8Rso+bpwcive/E/irQPBOgXfizxTeJYafp8ZmnnkOFRR+pJPAA5J4GTX8uP7Uf7R3hHWPilqfjDwNpo02HVZ8wWqL84jAAaVlHAeUguVHAY/Unnq1oU6bnVdkuv6GtOlKclCmtWfQn7X37bl7qEqaB4HRbaCyXytOssgiBTgedL281hzg9Onrn8xl1eNpJNQ1C4M00zGSSV23M7HqST1Oa5K7Np44uf7Psmkubp3M0hCjcG9ZGzjn1r3fwR8LrDRokvNaIlmABw3IU/l1/yBXwWZY+eJnzS0itl/XU+tweEjh4WW73ZmaHpev8AiNhJCjW9vjh2+8w9hXqVnpukeGoFe4cqVIJdjyT2/X86W+8U2ljG1rpCiRxxu6jNeO+IrbX9amEzuwERy3rz9eB/M15jdztSPQhr7ahrEt/b/Ky8bXHIz0bn1HPP4CvW/CsEUKNqOqybQ3zZb77n2z/OvnvS2khv4r5wZLiLAUADbx6jvjtmvWNGinvbn7TqjlyRnGcjNNR0DmPTZtYuNSxb2KlY+w7fX3rd063toHDTAzSj+H/H0+lYulwz3Moislx2yOD+n9KzvF3xX8C/DCIwXbi+1NvuWsJzhj/fPRRxVRRV7ntNlp91eRm61F1hgUZOTtVawtT+JGiaQDYeF4hf3J+Uyn/VqT6dya+GNb+M3j74i339n3rx2ttG2PItwwT2yxALH6ce9e3eGLXyLFcofN24wf6nsPYU9gP/1fof9kb9uf4LeBf2QYLjxVctDrujNcIdJAImuJHkYoIiRjaejFsbMHI6Zf8As+/BTxt+1l4/X9qT9pSPdpW4NoukOCIpI0OYyUPS3Q8qDzK3zNkdfkv/AIJ4/s0ah+0xZWv7Q3xmjik8PCZvstqgwuoSQnYC3cwJtwSeZmBz8uc/vtp3iPQrm+k0LT50M9oMNCo27QuFIXgAheAdudp4ODXzeyPfbIviJ4C8I/FLwnceCPHFmLvTrkdOjxOPuyRt/A69iPocgkV+dehfEbxr+wf4ot/hz8W5ZtZ+Hd+7DS9VRCz2h67CvoM/PFnK/eTK5FfpHrviHR/DejXXiDxBdRWNhYxNPcXEzBI4o0GWZieAAK/mw/bP/bF1n9pvxTH4a8DxvD4X0mZjYRSAo88pBQ3Uw6jKkiOP+FSc/MxxhUqQpwc6jtFFU6cqk1GCu2W/26f269Y+Nutnwv4KElv4cs5M2ds/ytcOOPtE2O/9xP4R7kmvz00L4e614kvTqniKR087qf429h/dH616Zofg/S/D+dW16Xz7k/MXbr9AO3+ea2W1q8v/AN1pqeTE3AYj5iPb/P4V8DmWZzxU9NILZfq/M+uwWCjh495dWa2jWPh7wZaC10+FFfsqD5ifUnrn3/WpbibUtVI+0Psj/uA449z/AErPjtrTTI/tepybc8kE5Ymse78UXV0fL0hPIjHG89fw9K8u3c7W7Grd38WjOEt0Blx95v4c+i/41iW5ur+VmvHIG7IHTmqscK2ymaY5dhnLck/Qf41sWlqyqZp3EWevrRZCu2bdgoUiCIYOeT3zXp2hrp+nRfb9VmWCFerP1PsB3NeNz+K9O0eApYr58q/xN0B9f/1VzZ1q/wBQv/td07Mw6lvT2HRR+pockhpHp3jv4q6pdwnR/CObKBxgyf8ALZx7dxXz5p+i6tPqryW8WXYHLSgso7/Oe59s16Np9rNdXTG0Uh24Z++Pr2H5n6V6LZ22k+HoFudVcBs/LHjv7L1J9z+lRzGyiM8IeCI7fZfXKguFUGRjkcDnaD/WvQrrxNYaQo0rSYzLMR25wfUn/P0rift2ueJG2RB7S3PRV/1jD3PRRj/9ddh4b0C1iJKAY/MZPv8AxH9KhS1Lt3P/1v0M/YPkRP2ZNBhiARY3uVCqMAAStwAOwr6budO8L6Fe3PjW/dbUQRySzTSSlII12jzJGBIRTtQbnwOBya+Lf+CevizRdf8A2VNH1vS7lJbQT3hMmcBQshJ3Z+7t75xivy7/AG6/27G+OWpy/CP4WXTL4OgfE9wmQ2qyIeG7H7MrfcU48w4Y8bRXy9ScYRc5uyW59BGEpT5YrUs/tq/tm6r+0x4gPw3+G9xJa+CdPmDM/KNqMiHiVxwfKB5ijPX77c4C/GVlc2eip9h0SLz5z94noD/tH/J9hXJaTZXUkGxT5MA5YZxnPdm9/QV1j3WmeHoFkuPk4+Vf4mHsP4R7n8q+DzTMpYufLHSC2/zZ9XgcFHDxu/iZaj0i6uZDqGtThtvJzwoqrdeJY7RzbaYuSRw7Dt7f5xXE6h4kvNamG/5YgcqmcLj1Pc/X8qtLAwQGPDyPzk9B/n0ryuXsdzkW5LyS4k8+/kLsx4X/AB/wFdFYxXVwyxW6gMe55Ix6Cm6Z4et7JTqevzCJj1Zu/wBBUepeNba3t2svDcflL/FPJyT9BSsuormlcPYaJGHuGM0zcjJrk7vUbvUZwJD8vO1Rwo/Dv+Ncvda3AP3s7FpHOSzHLE+w7Utm+q6uTFZDyo8/Mx6/WomWkb+baDDTPlj0ReSPqRnFdBpukXeptv2iK3Xl3Y4RQPU1mWkFhpOQF+0XAwCO+TnAJ6L9OvtViJNV8RShdTcC2j5ESfLEvufU/Xn0rNstRPQI9QtwgsfCyiYr/wAvLjEQP+yv8X1P610mj+H0WcXWpO1xeMOWbl8fyQU3RbbMSiyG1EH+sbjgf3R2+tdbamytrc3KnbErANM3OWPOAP4iR0qGzdGotg2xbNBktjKJk/p1P48fSuziOn6DBtuj5twekYP3Sf7xH8q8/ttbvriUw6eGgSTv/wAtZB65/hH0P49qll1PS9FJF0RLP2jU5xn1qLiP/9k=" - return Group { - UserProfile() - .environmentObject(chatModel1) - UserProfile() - .environmentObject(chatModel2) - } - } -} diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 160130bccc..887023b670 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -9,7 +9,6 @@ import SimpleXChat struct UserProfilesView: View { @EnvironmentObject private var m: ChatModel @EnvironmentObject private var theme: AppTheme - @Binding var showSettings: Bool @Environment(\.editMode) private var editMode @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true @@ -22,6 +21,7 @@ struct UserProfilesView: View { @State private var profileHidden = false @State private var profileAction: UserProfileAction? @State private var actionPassword = "" + @State private var navigateToProfileCreate = false var trimmedSearchTextOrPassword: String { searchTextOrPassword.trimmingCharacters(in: .whitespaces)} @@ -56,17 +56,6 @@ struct UserProfilesView: View { } var body: some View { - if authorized { - userProfilesView() - } else { - Button(action: runAuth) { Label("Unlock", systemImage: "lock") } - .onAppear(perform: runAuth) - } - } - - private func runAuth() { authorize(NSLocalizedString("Open user profiles", comment: "authentication reason"), $authorized) } - - private func userProfilesView() -> some View { List { if profileHidden { Button { @@ -78,12 +67,14 @@ struct UserProfilesView: View { Section { let users = filteredUsers() let v = ForEach(users) { u in - userView(u.user) + userView(u) } if #available(iOS 16, *) { v.onDelete { indexSet in if let i = indexSet.first { - confirmDeleteUser(users[i].user) + withAuth { + confirmDeleteUser(users[i].user) + } } } } else { @@ -91,13 +82,22 @@ struct UserProfilesView: View { } if trimmedSearchTextOrPassword == "" { - NavigationLink { - CreateProfile() - } label: { + NavigationLink( + destination: CreateProfile(), + isActive: $navigateToProfileCreate + ) { Label("Add profile", systemImage: "plus") + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 38) + .padding(.leading, 16).padding(.vertical, 8).padding(.trailing, 32) + .contentShape(Rectangle()) + .onTapGesture { + withAuth { + self.navigateToProfileCreate = true + } + } + .padding(.leading, -16).padding(.vertical, -8).padding(.trailing, -32) } - .frame(height: 44) - .padding(.vertical, 4) } } footer: { Text("Tap to activate profile.") @@ -191,7 +191,25 @@ struct UserProfilesView: View { private var visibleUsersCount: Int { m.users.filter({ u in !u.user.hidden }).count } - + + private func withAuth(_ action: @escaping () -> Void) { + if authorized { + action() + } else { + authenticate( + reason: NSLocalizedString("Change chat profiles", comment: "authentication reason") + ) { laResult in + switch laResult { + case .success, .unavailable: + authorized = true + AppSheetState.shared.scenePhaseActive = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: action) + case .failed: authorized = false + } + } + } + } + private func correctPassword(_ user: User, _ pwd: String) -> Bool { if let ph = user.viewPwdHash { return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash @@ -203,11 +221,11 @@ struct UserProfilesView: View { !user.hidden ? nil : trimmedSearchTextOrPassword } - @ViewBuilder private func profileActionView(_ action: UserProfileAction) -> some View { + private func profileActionView(_ action: UserProfileAction) -> some View { let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces) let passwordField = PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid) let actionEnabled: (User) -> Bool = { user in actionPassword != "" && passwordValid && correctPassword(user, actionPassword) } - List { + return List { switch action { case let .deleteUser(user, delSMPQueues): actionHeader("Delete profile", user) @@ -215,8 +233,10 @@ struct UserProfilesView: View { passwordField settingsRow("trash", color: theme.colors.secondary) { Button("Delete chat profile", role: .destructive) { - profileAction = nil - Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) } + withAuth { + profileAction = nil + Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) } + } } .disabled(!actionEnabled(user)) } @@ -233,8 +253,10 @@ struct UserProfilesView: View { passwordField settingsRow("lock.open", color: theme.colors.secondary) { Button("Unhide chat profile") { - profileAction = nil - setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: actionPassword) } + withAuth{ + profileAction = nil + setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: actionPassword) } + } } .disabled(!actionEnabled(user)) } @@ -257,11 +279,13 @@ struct UserProfilesView: View { private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View { Button(title, role: .destructive) { - if let user = userToDelete { - if passwordEntryRequired(user) { - profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues) - } else { - alert = .deleteUser(user: user, delSMPQueues: delSMPQueues) + withAuth { + if let user = userToDelete { + if passwordEntryRequired(user) { + profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues) + } else { + alert = .deleteUser(user: user, delSMPQueues: delSMPQueues) + } } } } @@ -274,6 +298,7 @@ struct UserProfilesView: View { private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async { do { if user.activeUser { + ChatModel.shared.removeWallpaperFilesFromAllChats(user) if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) { try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil) try await deleteUser() @@ -285,7 +310,7 @@ struct UserProfilesView: View { await MainActor.run { onboardingStageDefault.set(.step1_SimpleXInfo) m.onboardingStage = .step1_SimpleXInfo - showSettings = false + dismissAllSheets() } } } else { @@ -299,34 +324,43 @@ struct UserProfilesView: View { func deleteUser() async throws { try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: viewPwd) + removeWallpaperFilesFromTheme(user.uiThemes) await MainActor.run { withAnimation { m.removeUser(user) } } } } - @ViewBuilder private func userView(_ user: User) -> some View { + @ViewBuilder private func userView(_ userInfo: UserInfo) -> some View { + let user = userInfo.user let v = Button { Task { do { try await changeActiveUserAsync_(user.userId, viewPwd: userViewPassword(user)) + dismissAllSheets() } catch { await MainActor.run { alert = .activateUserError(error: responseError(error)) } } } } label: { HStack { - ProfileImage(imageStr: user.image, size: 44) - .padding(.vertical, 4) + ProfileImage(imageStr: user.image, size: 38) .padding(.trailing, 12) Text(user.chatViewName) Spacer() if user.activeUser { Image(systemName: "checkmark").foregroundColor(theme.colors.onBackground) - } else if user.hidden { - Image(systemName: "lock").foregroundColor(theme.colors.secondary) - } else if !user.showNtfs { - Image(systemName: "speaker.slash").foregroundColor(theme.colors.secondary) } else { - Image(systemName: "checkmark").foregroundColor(.clear) + if userInfo.unreadCount > 0 { + UnreadBadge(userInfo: userInfo) + } + if user.hidden { + Image(systemName: "lock").foregroundColor(theme.colors.secondary) + } else if userInfo.unreadCount == 0 { + if !user.showNtfs { + Image(systemName: "speaker.slash").foregroundColor(theme.colors.secondary) + } else { + Image(systemName: "checkmark").foregroundColor(.clear) + } + } } } } @@ -334,30 +368,38 @@ struct UserProfilesView: View { .swipeActions(edge: .leading, allowsFullSwipe: true) { if user.hidden { Button("Unhide") { - if passwordEntryRequired(user) { - profileAction = .unhideUser(user: user) - } else { - setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: trimmedSearchTextOrPassword) } + withAuth { + if passwordEntryRequired(user) { + profileAction = .unhideUser(user: user) + } else { + setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: trimmedSearchTextOrPassword) } + } } } .tint(.green) } else { if visibleUsersCount > 1 { Button("Hide") { - selectedUser = user + withAuth { + selectedUser = user + } } .tint(.gray) } Group { if user.showNtfs { Button("Mute") { - setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) { - try await apiMuteUser(user.userId) + withAuth { + setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) { + try await apiMuteUser(user.userId) + } } } } else { Button("Unmute") { - setUserPrivacy(user) { try await apiUnmuteUser(user.userId) } + withAuth { + setUserPrivacy(user) { try await apiUnmuteUser(user.userId) } + } } } } @@ -369,7 +411,9 @@ struct UserProfilesView: View { } else { v.swipeActions(edge: .trailing, allowsFullSwipe: true) { Button("Delete", role: .destructive) { - confirmDeleteUser(user) + withAuth { + confirmDeleteUser(user) + } } } } @@ -406,8 +450,15 @@ public func chatPasswordHash(_ pwd: String, _ salt: String) -> String { return hash } +public func correctPassword(_ user: User, _ pwd: String) -> Bool { + if let ph = user.viewPwdHash { + return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash + } + return false +} + struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { - UserProfilesView(showSettings: Binding.constant(true)) + UserProfilesView() } } diff --git a/apps/ios/SimpleX (iOS).entitlements b/apps/ios/SimpleX (iOS).entitlements index c78a7cb941..2ec32def0a 100644 --- a/apps/ios/SimpleX (iOS).entitlements +++ b/apps/ios/SimpleX (iOS).entitlements @@ -9,6 +9,10 @@ applinks:simplex.chat applinks:www.simplex.chat applinks:simplex.chat?mode=developer + applinks:*.simplex.im + applinks:*.simplex.im?mode=developer + applinks:*.simplexonflux.com + applinks:*.simplexonflux.com?mode=developer com.apple.security.application-groups diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 40481d81f1..e965e5a1a5 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -49,7 +49,7 @@ %@ - %@ + %@ No comment provided by engineer. @@ -187,23 +187,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - ** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بكم. - No comment provided by engineer. - **Create link / QR code** for your contact to use. ** أنشئ رابطًا / رمز QR ** لتستخدمه جهة الاتصال الخاصة بك. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. ** المزيد من الخصوصية **: تحققوا من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديكم. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. ** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامكم للتطبيق). No comment provided by engineer. @@ -217,8 +212,8 @@ ** يرجى ملاحظة **: لن تتمكنوا من استعادة أو تغيير عبارة المرور إذا فقدتموها. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. ** موصى به **: يتم إرسال رمز الجهاز والإشعارات إلى خادم إشعارات SimpleX Chat ، ولكن ليس محتوى الرسالة أو حجمها أو مصدرها. No comment provided by engineer. @@ -384,292 +379,356 @@ أضف إلى جهاز آخر No comment provided by engineer. - + Admins can create the links to join groups. + يمكن للمُدراء إنشاء روابط للانضمام إلى المجموعات. No comment provided by engineer. - + Advanced network settings + إعدادات الشبكة المتقدمة No comment provided by engineer. - + All chats and messages will be deleted - this cannot be undone! + سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! No comment provided by engineer. - + All group members will remain connected. + سيبقى جميع أعضاء المجموعة على اتصال. No comment provided by engineer. - + All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. + سيتم حذف جميع الرسائل - لا يمكن التراجع عن هذا! سيتم حذف الرسائل فقط من أجلك. No comment provided by engineer. All your contacts will remain connected No comment provided by engineer. - + Allow + سماح No comment provided by engineer. - + Allow disappearing messages only if your contact allows it to you. + السماح بالرسائل المختفية فقط إذا سمحت لك جهة الاتصال بذلك. No comment provided by engineer. Allow irreversible message deletion only if your contact allows it to you. No comment provided by engineer. - + Allow sending direct messages to members. + السماح بإرسال رسائل مباشرة إلى الأعضاء. No comment provided by engineer. - + Allow sending disappearing messages. + السماح بإرسال الرسائل التي تختفي. No comment provided by engineer. Allow to irreversibly delete sent messages. No comment provided by engineer. - + Allow to send voice messages. + السماح بإرسال رسائل صوتية. No comment provided by engineer. - + Allow voice messages only if your contact allows them. + اسمح بالرسائل الصوتية فقط إذا سمحت جهة اتصالك بذلك. No comment provided by engineer. - + Allow voice messages? + السماح بالرسائل الصوتية؟ No comment provided by engineer. Allow your contacts to irreversibly delete sent messages. No comment provided by engineer. - + Allow your contacts to send disappearing messages. + السماح لجهات اتصالك بإرسال رسائل تختفي. No comment provided by engineer. - + Allow your contacts to send voice messages. + اسمح لجهات اتصالك بإرسال رسائل صوتية. No comment provided by engineer. - + Already connected? + متصل بالفعل؟ No comment provided by engineer. - + Answer call + أجب الاتصال No comment provided by engineer. - + App build: %@ + إصدار التطبيق: %@ No comment provided by engineer. - + App icon + رمز التطبيق No comment provided by engineer. - + App version + نسخة التطبيق No comment provided by engineer. - + App version: v%@ + نسخة التطبيق: v%@ No comment provided by engineer. - + Appearance + المظهر No comment provided by engineer. - + Attach + إرفاق No comment provided by engineer. - + Audio & video calls + مكالمات الصوت والفيديو No comment provided by engineer. - + Authentication failed + فشلت المصادقة No comment provided by engineer. - + Authentication unavailable + المصادقة غير متاحة No comment provided by engineer. - + Auto-accept contact requests + قبول طلبات الاتصال تلقائيًا No comment provided by engineer. - + Auto-accept images + قبول تلقائي للصور No comment provided by engineer. Automatically No comment provided by engineer. - + Back + رجوع No comment provided by engineer. Both you and your contact can irreversibly delete sent messages. No comment provided by engineer. - + Both you and your contact can send disappearing messages. + يمكنك أنت وجهة اتصالك إرسال رسائل تختفي. No comment provided by engineer. - + Both you and your contact can send voice messages. + يمكنك أنت وجهة اتصالك إرسال رسائل صوتية. 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. - + Call already ended! + انتهت المكالمة بالفعل! No comment provided by engineer. - + Calls + المكالمات No comment provided by engineer. - + Can't invite contact! + لا يمكن دعوة جهة اتصال! No comment provided by engineer. - + Can't invite contacts! + لا يمكن دعوة جهات الاتصال! No comment provided by engineer. - + Cancel + إلغاء No comment provided by engineer. - + Cannot access keychain to save database password + لا يمكن الوصول إلى سلسلة المفاتيح لحفظ كلمة مرور قاعدة البيانات No comment provided by engineer. - + Cannot receive file + لا يمكن استلام الملف No comment provided by engineer. - + Change + تغير No comment provided by engineer. - + Change database passphrase? + تغيير عبارة مرور قاعدة البيانات؟ No comment provided by engineer. - + Change member role? + تغيير دور العضو؟ No comment provided by engineer. - + Change receiving address + تغيير عنوان الاستلام No comment provided by engineer. - + Change receiving address? + تغيير عنوان الاستلام؟ No comment provided by engineer. - + Change role + تغيير الدور No comment provided by engineer. Chat archive No comment provided by engineer. - + Chat console + وحدة تحكم الدردشة No comment provided by engineer. - + Chat database + قاعدة بيانات الدردشة No comment provided by engineer. - + Chat database deleted + حُذفت قاعدة بيانات الدردشة No comment provided by engineer. - + Chat database imported + استُوردت قاعدة بيانات الدردشة No comment provided by engineer. - + Chat is running + الدردشة قيد التشغيل No comment provided by engineer. - + Chat is stopped + توقفت الدردشة No comment provided by engineer. - + Chat preferences + تفضيلات الدردشة No comment provided by engineer. - + Chats + الدردشات No comment provided by engineer. - + Check server address and try again. + تحقق من عنوان الخادم وحاول مرة أخرى. No comment provided by engineer. - + Choose file + اختر الملف No comment provided by engineer. - + Choose from library + اختر من المكتبة No comment provided by engineer. - + Clear + مسح No comment provided by engineer. - + Clear conversation + مسح الدردشة No comment provided by engineer. - + Clear conversation? + مسح الدردشة؟ No comment provided by engineer. - + Clear verification + امسح التحقُّق No comment provided by engineer. Colors No comment provided by engineer. - + Compare security codes with your contacts. + قارن رموز الأمان مع جهات اتصالك. No comment provided by engineer. - + Configure ICE servers + ضبط خوادم ICE No comment provided by engineer. - + Confirm + تأكيد No comment provided by engineer. - + Confirm new passphrase… + تأكيد عبارة المرور الجديدة… No comment provided by engineer. - + Connect + اتصل server test step @@ -680,8 +739,9 @@ Connect via group link? No comment provided by engineer. - + Connect via link + تواصل عبر الرابط No comment provided by engineer. @@ -696,224 +756,273 @@ Connect via relay No comment provided by engineer. - + Connecting to server… + جارِ الاتصال بالخادم… No comment provided by engineer. - + Connecting to server… (error: %@) + الاتصال بالخادم... (الخطأ: %@) No comment provided by engineer. - + Connection + الاتصال No comment provided by engineer. - + Connection error + خطأ في الإتصال No comment provided by engineer. - + Connection error (AUTH) + خطأ في الإتصال (المصادقة) No comment provided by engineer. Connection request No comment provided by engineer. - + Connection request sent! + أرسلت طلب الاتصال! No comment provided by engineer. - + Connection timeout + انتهت مهلة الاتصال No comment provided by engineer. - + Contact allows + تسمح جهة الاتصال No comment provided by engineer. - + Contact already exists + جهة الاتصال موجودة بالفعل No comment provided by engineer. Contact and all messages will be deleted - this cannot be undone! No comment provided by engineer. - + Contact hidden: + جهة الاتصال مخفية: notification - + Contact is connected + تم الاتصال notification Contact is not connected yet! No comment provided by engineer. - + Contact name + اسم جهة الاتصال No comment provided by engineer. - + Contact preferences + تفضيلات جهة الاتصال No comment provided by engineer. Contact requests No comment provided by engineer. - + Contacts can mark messages for deletion; you will be able to view them. + يمكن لجهات الاتصال تحديد الرسائل لحذفها؛ ستتمكن من مشاهدتها. No comment provided by engineer. - + Copy + نسخ chat item action Core built at: %@ No comment provided by engineer. - + Core version: v%@ + الإصدار الأساسي: v%@ No comment provided by engineer. - + Create + إنشاء No comment provided by engineer. Create address No comment provided by engineer. - + Create group link + إنشاء رابط المجموعة No comment provided by engineer. - + Create link + إنشاء رابط No comment provided by engineer. Create one-time invitation link No comment provided by engineer. - + Create queue + إنشاء قائمة انتظار server test step - + Create secret group + إنشاء مجموعة سرية No comment provided by engineer. - + Create your profile + أنشئ ملف تعريفك No comment provided by engineer. Created on %@ No comment provided by engineer. - + Current passphrase… + عبارة المرور الحالية… No comment provided by engineer. - + Currently maximum supported file size is %@. + الحد الأقصى لحجم الملف المدعوم حاليًا هو %@. No comment provided by engineer. - + Dark + داكن No comment provided by engineer. - + Database ID + معرّف قاعدة البيانات No comment provided by engineer. - + Database encrypted! + قاعدة البيانات مُعمّاة! No comment provided by engineer. - + Database encryption passphrase will be updated and stored in the keychain. + سيتم تحديث عبارة المرور الخاصة بتشفير قاعدة البيانات وتخزينها في سلسلة المفاتيح. + No comment provided by engineer. - + Database encryption passphrase will be updated. + سيتم تحديث عبارة مرور تعمية قاعدة البيانات. + No comment provided by engineer. - + Database error + خطأ في قاعدة البيانات No comment provided by engineer. - + Database is encrypted using a random passphrase, you can change it. + قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية، يمكنك تغييرها. No comment provided by engineer. - + Database is encrypted using a random passphrase. Please change it before exporting. + قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية. يُرجى تغييره قبل التصدير. No comment provided by engineer. - + Database passphrase + عبارة مرور قاعدة البيانات No comment provided by engineer. - + Database passphrase & export + عبارة مرور قاعدة البيانات وتصديرها No comment provided by engineer. - + Database passphrase is different from saved in the keychain. + عبارة المرور الخاصة بقاعدة البيانات مختلفة عن تلك المحفوظة في سلسلة المفاتيح. No comment provided by engineer. - + Database passphrase is required to open chat. + عبارة مرور قاعدة البيانات مطلوبة لفتح الدردشة. No comment provided by engineer. - + Database will be encrypted and the passphrase stored in the keychain. + سيتم تشفير قاعدة البيانات وتخزين عبارة المرور في سلسلة المفاتيح. + No comment provided by engineer. - + Database will be encrypted. + سيتم تعمية قاعدة البيانات. + No comment provided by engineer. - + Database will be migrated when the app restarts + سيتم نقل قاعدة البيانات عند إعادة تشغيل التطبيق No comment provided by engineer. - + Decentralized + لامركزي No comment provided by engineer. - + Delete + حذف chat item action Delete Contact No comment provided by engineer. - + Delete address + حذف العنوان No comment provided by engineer. - + Delete address? + حذف العنوان؟ No comment provided by engineer. - + Delete after + حذف بعد No comment provided by engineer. - + Delete all files + حذف جميع الملفات No comment provided by engineer. @@ -924,152 +1033,188 @@ Delete chat archive? No comment provided by engineer. - + Delete chat profile? + حذف ملف تعريف الدردشة؟ No comment provided by engineer. - + Delete connection + حذف الاتصال No comment provided by engineer. - + Delete contact + حذف جهة الاتصال No comment provided by engineer. - + Delete contact? + حذف جهة الاتصال؟ No comment provided by engineer. - + Delete database + حذف قاعدة البيانات No comment provided by engineer. - + Delete files and media? + حذف الملفات والوسائط؟ No comment provided by engineer. - + Delete files for all chat profiles + حذف الملفات لجميع ملفات تعريف الدردشة No comment provided by engineer. - + Delete for everyone + حذف للجميع chat feature - + Delete for me + حذف بالنسبة لي No comment provided by engineer. - + Delete group + حذف المجموعة No comment provided by engineer. - + Delete group? + حذف المجموعة؟ No comment provided by engineer. - + Delete invitation + حذف الدعوة No comment provided by engineer. - + Delete link + حذف الرابط No comment provided by engineer. - + Delete link? + حذف الرابط؟ No comment provided by engineer. - + Delete message? + حذف الرسالة؟ No comment provided by engineer. - + Delete messages + حذف الرسائل No comment provided by engineer. - + Delete messages after + حذف الرسائل بعد No comment provided by engineer. - + Delete old database + حذف قاعدة البيانات القديمة No comment provided by engineer. - + Delete old database? + حذف قاعدة البيانات القديمة؟ No comment provided by engineer. Delete pending connection No comment provided by engineer. - + Delete pending connection? + حذف الاتصال قيد الانتظار؟ No comment provided by engineer. - + Delete queue + حذف قائمة الانتظار server test step - + Delete user profile? + حذف ملف تعريف المستخدم؟ No comment provided by engineer. - + Description + الوصف No comment provided by engineer. - + Develop + يطور No comment provided by engineer. - + Developer tools + أدوات المطور No comment provided by engineer. - + Device + الجهاز No comment provided by engineer. - + Device authentication is disabled. Turning off SimpleX Lock. + استيثاق الجهاز مُعطَّل. جارِ إيقاف تشغيل قفل SimpleX. No comment provided by engineer. - + Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication. + مصادقة الجهاز غير مفعّلة. يمكنك تشغيل قفل SimpleX عبر الإعدادات، بمجرد تفعيل مصادقة الجهاز. No comment provided by engineer. - + Different names, avatars and transport isolation. + أسماء مختلفة، صور الأفاتار وعزل النقل. No comment provided by engineer. - + Direct messages + رسائل مباشرة chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. + الرسائل المباشرة بين الأعضاء ممنوعة. No comment provided by engineer. - + Disable SimpleX Lock + تعطيل قفل SimpleX authentication reason - + Disappearing messages + الرسائل المختفية chat feature - + Disappearing messages are prohibited in this chat. + يُحظر اختفاء الرسائل في هذه الدردشة. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. + الرسائل المختفية ممنوعة. No comment provided by engineer. - + Disconnect + قطع الاتصال server test step @@ -1080,124 +1225,153 @@ Display name: No comment provided by engineer. - + Do NOT use SimpleX for emergency calls. + لا تستخدم SimpleX لإجراء مكالمات الطوارئ. No comment provided by engineer. - + Do it later + افعل ذلك لاحقا No comment provided by engineer. - + Duplicate display name! + اسم العرض مكرر! No comment provided by engineer. - + Edit + تحرير chat item action - + Edit group profile + حرّر ملف تعريف المجموعة No comment provided by engineer. - + Enable + تفعيل No comment provided by engineer. - + Enable SimpleX Lock + تفعيل قفل SimpleX authentication reason - + Enable TCP keep-alive + تفعيل أبقِ TCP على قيد الحياة No comment provided by engineer. - + Enable automatic message deletion? + تفعيل الحذف التلقائي للرسائل؟ No comment provided by engineer. - + Enable instant notifications? + تفعيل الإشعارات فورية؟ No comment provided by engineer. - + Enable notifications + تفعيل الإشعارات No comment provided by engineer. - + Enable periodic notifications? + تفعيل الإشعارات دورية؟ No comment provided by engineer. - + Encrypt + التشفير No comment provided by engineer. - + Encrypt database? + تشفير قاعدة البيانات؟ No comment provided by engineer. - + Encrypted database + قاعدة بيانات مشفرة No comment provided by engineer. - + Encrypted message or another event + رسالة مشفرة أو حدث آخر notification - + Encrypted message: database error + رسالة مشفرة: خطأ في قاعدة البيانات notification - + Encrypted message: keychain error + رسالة مشفرة: خطأ في سلسلة المفاتيح notification - + Encrypted message: no passphrase + الرسالة المشفرة: لا توجد عبارة مرور notification - + Encrypted message: unexpected error + رسالة مشفرة: خطأ غير متوقع notification - + Enter correct passphrase. + أدخل عبارة المرور الصحيحة. No comment provided by engineer. - + Enter passphrase… + أدخل عبارة المرور… No comment provided by engineer. - + Enter server manually + أدخل الخادم يدوياً No comment provided by engineer. - + Error + خطأ No comment provided by engineer. - + Error accepting contact request + خطأ في قبول طلب الاتصال No comment provided by engineer. Error accessing database file No comment provided by engineer. - + Error adding member(s) + خطأ في إضافة عضو (أعضاء) No comment provided by engineer. - + Error changing address + خطأ في تغيير العنوان No comment provided by engineer. - + Error changing role + خطأ في تغيير الدور المتغير No comment provided by engineer. - + Error changing setting + خطأ في تغيير الإعدادات No comment provided by engineer. @@ -1428,16 +1602,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1528,8 +1702,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1625,8 +1799,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -1926,8 +2100,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -1978,8 +2152,9 @@ We will be adding server redundancy to prevent lost messages. Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. + يمكن لأي شخص استضافة الخوادم. No comment provided by engineer. @@ -2010,8 +2185,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2382,96 +2557,117 @@ We will be adding server redundancy to prevent lost messages. Sent messages will be deleted after set time. No comment provided by engineer. - + Server requires authorization to create queues, check password + يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور server test error - + Server test failed! + فشلت تجربة الخادم! No comment provided by engineer. - + Servers + الخوادم No comment provided by engineer. - + Set 1 day + تعيين يوم واحد No comment provided by engineer. - + Set contact name… + تعيين اسم جهة الاتصال… No comment provided by engineer. - + Set group preferences + عيّن تفضيلات المجموعة No comment provided by engineer. - + Set passphrase to export + عيّن عبارة المرور للتصدير No comment provided by engineer. - + Set timeouts for proxy/VPN + حدد مهلات للوسيط او شبكات افتراضية خاصة (Proxy/VPN timeouts) No comment provided by engineer. - + Settings + الإعدادات No comment provided by engineer. - + Share + مشاركة chat item action Share invitation link No comment provided by engineer. - + Share link + مشاركة الرابط No comment provided by engineer. Share one-time invitation link No comment provided by engineer. - + Show QR code + عرض رمز QR No comment provided by engineer. - + Show preview + عرض المعاينة No comment provided by engineer. - + SimpleX Chat security was audited by Trail of Bits. + تم تدقيق أمان SimpleX Chat بواسطة Trail of Bits. No comment provided by engineer. - + SimpleX Lock + قفل SimpleX No comment provided by engineer. - + SimpleX Lock turned on + تم تشغيل القفل SimpleX No comment provided by engineer. - + SimpleX contact address + عنوان جهة أتصال SimpleX simplex link type - + SimpleX encrypted message or connection event + حَدَثْ SimpleX لرسالة أو اتصال مشفر notification - + SimpleX group link + رابط مجموعة SimpleX simplex link type - + SimpleX links + روابط SimpleX No comment provided by engineer. - + SimpleX one-time invitation + دعوة SimpleX لمرة واحدة simplex link type @@ -2590,8 +2786,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2622,16 +2818,16 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -2686,8 +2882,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2860,8 +3056,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -2972,10 +3168,6 @@ To connect, please ask your contact to create another connection link and check You can use markdown to format messages: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -3535,72 +3727,87 @@ SimpleX servers cannot see your profile. secret No comment provided by engineer. - + starting… + يبدأ… No comment provided by engineer. - + strike + شطب No comment provided by engineer. this contact notification title - + unknown + غير معروف connection info - + updated group profile + حدثت ملف تعريف المجموعة rcv group event chat item v%@ (%@) No comment provided by engineer. - + via contact address link + عبر رابط عنوان الاتصال chat list item description - + via group link + عبر رابط المجموعة chat list item description - + via one-time link + عبر رابط لمرة واحدة chat list item description - + via relay + عبر المُرحل No comment provided by engineer. - + video call (not e2e encrypted) + مكالمة الفيديو ليست مُعمّاة بين الطريفين No comment provided by engineer. - + waiting for answer… + بانتظار الرد… No comment provided by engineer. - + waiting for confirmation… + في انتظار التأكيد… No comment provided by engineer. - + wants to connect to you! + يريد الاتصال بك! No comment provided by engineer. - + yes + نعم pref value - + you are invited to group + أنت مدعو إلى المجموعة No comment provided by engineer. - + you changed address + غيّرتَ العنوان chat item text @@ -3615,16 +3822,18 @@ SimpleX servers cannot see your profile. you changed role of %1$@ to %2$@ snd group event chat item - + you left + غادرت snd group event chat item you removed %@ snd group event chat item - + you shared one-time link + لقد شاركت رابط لمرة واحدة chat list item description @@ -3666,7 +3875,7 @@ SimpleX servers cannot see your profile. # %@ - # %@ + # %@ copied message info title, # <title> @@ -3752,8 +3961,8 @@ SimpleX servers cannot see your profile. %u messages skipped. %u تم تخطي الرسائل. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **إضافة جهة اتصال**: لإنشاء رابط دعوة جديد، أو الاتصال عبر الرابط الذي تلقيتوهم. @@ -3886,6 +4095,1668 @@ SimpleX servers cannot see your profile. Active connections اتصالات نشطة + + Apply + طبّق + + + %@ server + %@ خادم + + + Accept conditions + اقبل الشروط + + + Share address + مشاركة العنوان + + + Already connecting! + جارٍ الاتصال بالفعل! + + + %d file(s) are still being downloaded. + %d الملف(ات) لا تزال قيد التنزيل. + + + %d file(s) failed to download. + %d الملف(ات) فشلت في التنزيل. + + + All app data is deleted. + حُذفت جميع بيانات التطبيق. + + + Allow irreversible message deletion only if your contact allows it to you. (24 hours) + السماح بحذف الرسائل بشكل لا رجوع فيه فقط إذا سمحت لك جهة الاتصال بذلك. (24 ساعة) + + + Share profile + شارك ملف التعريف + + + Always use relay + استخدم الموجه دائمًا + + + Address + عنوان + + + All data is erased when it is entered. + يتم مسح جميع البيانات عند إدخالها. + + + %d file(s) were deleted. + %d تم حذف الملف(ات). + + + %d file(s) were not downloaded. + %d لم يتم تنزيل الملف(ات). + + + %d messages not forwarded + %d الرسائل لم يتم تحويلها + + + %d seconds(s) + %d ثواني + + + **Scan / Paste link**: to connect via a link you received. + **امسح / ألصِق الرابط**: للاتصال عبر الرابط الذي تلقيته. + + + 1 year + سنة واحدة + + + 1-time link + رابط لمرة واحدة + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + يمكن استعمال الرابط لمرة واحدة *مع جهة اتصال واحدة فقط* - شاركه شخصياً أو عبر أي تطبيق مراسلة. + + + Accent + لون تمييزي + + + Accepted conditions + الشروط المتفق عليها + + + All chats will be removed from the list (text), and the list deleted. + سيتم إزالة جميع الدردشات من القائمة (النص)، وحذف القائمة. + + + Allow message reactions. + السماح بردود الفعل على الرسائل. + + + Allow to irreversibly delete sent messages. (24 hours) + السماح بحذف الرسائل المرسلة بشكل لا رجعة فيه. (24 ساعة) + + + Allow to send SimpleX links. + السماح بإرسال روابط SimpleX. + + + Already joining the group! + جارٍ انضمام بالفعل إلى المجموعة! + + + An empty chat profile with the provided name is created, and the app opens as usual. + يتم إنشاء ملف تعريف دردشة فارغ بالاسم المقدم، ويفتح التطبيق كالمعتاد. + + + Authentication cancelled + ألغيت المصادقة + + + Audio/video calls are prohibited. + مكالمات الصوت/الفيديو محظورة. + + + Better groups + مجموعات أفضل + + + Background + الخلفية + + + Better calls + مكالمات أفضل + + + Both you and your contact can irreversibly delete sent messages. (24 hours) + يمكنك أنت وجهة اتصالك حذف الرسائل المرسلة بشكل لا رجعة فيه. (24 ساعة) + + + Block member for all? + حظر العضو للجميع؟ + + + Blur media + تمويه الوسائط + + + Server type + نوع الخادم + + + Server requires authorization to upload, check password + يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور + + + Server version is incompatible with network settings. + إصدار الخادم غير متوافق مع إعدادات الشبكة. + + + Share with contacts + مشاركة مع جهات الاتصال + + + Show: + عرض: + + + SimpleX Address + عنوان SimpleX + + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + توصلت SimpleX Chat وFlux إلى اتفاق لتضمين الخوادم التي تديرها Flux في التطبيق. + + + Allow calls? + السماح بالمكالمات؟ + + + App passcode is replaced with self-destruct passcode. + يتم استبدال رمز مرور التطبيق برمز مرور التدمير الذاتي. + + + SimpleX Lock mode + SimpleX وضع القفل + + + Audio and video calls + مكالمات الصوت والفيديو + + + App passcode + رمز مرور التطبيق + + + Bad message ID + معرّف رسالة سيئ + + + Server address is incompatible with network settings. + عنوان الخادم غير متوافق مع إعدادات الشبكة. + + + Servers statistics will be reset - this cannot be undone! + سيتم تصفير إحصائيات الخوادم - لا يمكن التراجع عن هذا! + + + Allow to send files and media. + السماح بإرسال الملفات والوسائط. + + + App encrypts new local files (except videos). + يُعمِّي الملفات المحلية الجديدة (باستثناء مقاطع الفيديو). + + + Better messages + رسائل أفضل + + + Set passcode + عيّن رمز المرور + + + Additional accent 2 + لون إضافي ثانوي 2 + + + Allow your contacts adding message reactions. + السماح لجهات اتصالك بإضافة ردود الفعل للرسالة. + + + Allow your contacts to call you. + السماح لجهات اتصالك بالاتصال بك. + + + Audio/video calls + مكالمات الصوت/الفيديو + + + Better notifications + إشعارات أفضل + + + Better user experience + تجربة مستخدم أفضل + + + Block + حظر + + + Black + أسود + + + Block member? + حظر العضو؟ + + + Blocked by admin + محظور من قبل المُدير + + + Blur for better privacy. + تمويه من أجل خصوصية أفضل. + + + Show → on messages sent via private routing. + عرض ← على الرسائل المرسلة عبر التوجيه الخاص. + + + Share from other apps. + المشاركة من التطبيقات الأخرى. + + + Share this 1-time invite link + شارك رابط الدعوة هذا لمرة واحدة + + + Set passphrase + عيّن عبارة المرور + + + Share address with contacts? + مشاركة العنوان مع جهات الاتصال؟ + + + Allow downgrade + السماح بالرجوع إلى إصدار سابق + + + Bad desktop address + عنوان سطح المكتب غير صالح + + + %1$@, %2$@ + %1$@, %2$@ + + + All profiles + جميع ملفات التعريف + + + Authentication is required before the call is connected, but you may miss calls. + يتطلب التوثيق قبل الاتصال بالمكالمة، ولكن قد تفوتك المكالمات. + + + Archiving database + جارِ أرشفة قاعدة البيانات + + + Settings were changed. + تم تغيير الإعدادات. + + + Better groups performance + أداء مجموعات أفضل + + + Better privacy and security + خصوصية وأمان أفضل + + + Better security ✅ + أمان أفضل ✅ + + + Block for all + حظر للجميع + + + Block group members + حظر أعضاء المجموعة + + + Block member + حظر العضو + + + Both you and your contact can add message reactions. + يمكنك أنت وجهة اتصالك إضافة ردود فعل الرسائل. + + + Both you and your contact can make calls. + يمكنك أنت وجهة الاتصال إجراء مكالمات. + + + Server + الخادم + + + Server operators + مُشغلي الخادم + + + Server version is incompatible with your app: %@. + إصدار الخادم غير متوافق مع التطبيق لديك: %@. + + + Servers info + معلومات الخوادم + + + Set chat name… + عيّن اسم الدردشة… + + + Shape profile images + شكّل الصور التعريفية + + + Share address publicly + شارك العنوان علناً + + + Show developer options + عرض خيارات المطور + + + SimpleX address + عنوان SimpleX + + + SimpleX address or 1-time link? + عنوان SimpleX أو رابط لمرة واحدة؟ + + + @'%@' + @'%@' + + + @%@ + @%@ + + + Active + نشط + + + Add friends + أضف أصدقاء + + + Add list + أضف القائمة + + + Address change will be aborted. Old receiving address will be used. + سيتم إحباط تغيير العنوان. سيتم استخدام عنوان الاستلام القديم. + + + All messages will be deleted - this cannot be undone! + سيتم حذف كافة الرسائل - لا يمكن التراجع عن هذا! + + + All reports will be archived for you. + سيتم أرشفة كافة البلاغات لك. + + + All your contacts will remain connected. + ستبقى جميع جهات اتصالك متصلة. + + + All your contacts will remain connected. Profile update will be sent to your contacts. + ستبقى جميع جهات اتصالك متصلة. سيتم إرسال تحديث ملف التعريف إلى جهات اتصالك. + + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + جميع جهات الاتصال، المحادثات والملفات الخاصة بك سيتم تشفيرها بأمان ورفعها على شكل أجزاء إلى موجهات XFTP المُعدة. + + + Allow calls only if your contact allows them. + السماح بالمكالمات فقط إذا سمحت جهة اتصالك بذلك. + + + Allow message reactions only if your contact allows them. + اسمح بردود الفعل على الرسائل فقط إذا سمحت جهة اتصالك بذلك. + + + Allow to report messsages to moderators. + السماح بالإبلاغ عن الرسائل إلى المشرفين. + + + Allow your contacts to irreversibly delete sent messages. (24 hours) + اسمح لجهات اتصالك بحذف الرسائل المرسلة بشكل لا رجعة فيه. (24 ساعة) + + + Another reason + سبب آخر + + + App group: + مجموعة التطبيق: + + + Apply to + طبّق لِ + + + Archive + أرشف + + + Archive %lld reports? + أرشف تقارير %lld؟ + + + Archive all reports? + أرشفة كافة البلاغات؟ + + + Archive and upload + أرشفة و رفع + + + Archive report + أرشف البلاغ + + + Archive report? + أرشف البلاغ؟ + + + Archive reports + أرشف البلاغات + + + Ask + اسأل + + + Auto-accept settings + إعدادات القبول التلقائي + + + Better message dates. + تواريخ أفضل للرسائل. + + + Server added to operator %@. + تمت إضافة الخادم إلى المشغل %@. + + + Server address is incompatible with network settings: %@. + عنوان الخادم غير متوافق مع إعدادات الشبكة: %@. + + + Server protocol changed. + تغيّر بروتوكول الخادم. + + + SimpleX links are prohibited. + روابط SimpleX محظورة. + + + Additional accent + لون إضافي ثانوي + + + Always use private routing. + استخدم دائمًا التوجيه الخاص. + + + About operators + عن المُشغلين + + + Add team members + أضف أعضاء الفريق + + + Added media & file servers + أُضيفت خوادم الوسائط والملفات + + + Added message servers + أُضيفت خوادم الرسائل + + + Address or 1-time link? + عنوان أو رابط لمرة واحدة؟ + + + Address settings + إعدادات العنوان + + + Allow sharing + السماح بالمشاركة + + + App data migration + ترحيل بيانات التطبيق + + + Archive contacts to chat later. + أرشفة جهات الاتصال للدردشة لاحقًا. + + + Better networking + اتصال أفضل + + + Session code + رمز الجلسة + + + Set default theme + تعيين السمة الافتراضية + + + Set it instead of system authentication. + عيّنها بدلاً من استيثاق النظام. + + + Set the message shown to new members! + تعيين رسالة تظهر للأعضاء الجدد! + + + Share 1-time link + مشاركة رابط ذو استخدام واحد + + + Share 1-time link with a friend + شارك رابطًا لمرة واحدة مع صديق + + + Share SimpleX address on social media. + شارك عنوان SimpleX على وسائل التواصل الاجتماعي. + + + Share to SimpleX + المشاركة لSimpleX + + + Show calls in phone history + عرض المكالمات في سجل الهاتف + + + Show percentage + أظهِر النسبة المئوية + + + SimpleX + SimpleX + + + SimpleX Lock not enabled! + قفل SimpleX غير مفعّل! + + + Bad message hash + تجزئة رسالة سيئة + + + App session + جلسة التطبيق + + + SimpleX links not allowed + روابط SimpleX غير مسموح بها + + + All data is kept private on your device. + جميع البيانات تُحفظ بشكل خاص على جهازك. + + + Archived contacts + جهات الاتصال المؤرشفة + + + Show message status + أظهِر حالة الرسالة + + + Set message expiration in chats. + اضبط انتهاء صلاحية الرسالة في الدردشات. + + + Server address + عنوان الخادم + + + Show last messages + إظهار الرسائل الأخيرة + + + Server operator changed. + تغيّر مُشغل الخادم. + + + SimpleX address and 1-time links are safe to share via any messenger. + عنوان SimpleX والروابط لمرة واحدة آمنة للمشاركة عبر أي برنامج مُراسلة. + + + Add your team members to the conversations. + أضف أعضاء فريقك إلى المحادثات. + + + Advanced settings + إعدادات متقدّمة + + + Add to list + أضف إلى القائمة + + + Additional secondary + ثانوي إضافي + + + Admins can block a member for all. + يمكن للمُدراء حظر عضو للجميع. + + + All + الكل + + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + جميع الرسائل والملفات تُرسل **مشفرة من النهاية-إلى-النهاية**، مع أمان ما-بعد-الحوسبة-الكمية في الرسائل المباشرة. + + + All new messages from %@ will be hidden! + جميع الرسائل الجديدة من %@ سيتم إخفاؤها! + + + Auto-accept + قبول تلقائي + + + Change self-destruct mode + تغيير وضع التدمير الذاتي + + + Chat database exported + صُدرت قاعدة بيانات الدردشة + + + Businesses + الشركات + + + Change automatic message deletion? + تغيير حذف الرسائل التلقائي؟ + + + Can't call contact + لا يمكن مكالمة جهة الاتصال + + + Chat list + قائمة الدردشات + + + Calls prohibited! + المكالمات ممنوعة! + + + Change lock mode + تغيير وضع القفل + + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + توقفت الدردشة. إذا كنت قد استخدمت قاعدة البيانات هذه بالفعل على جهاز آخر، فيجب عليك نقلها مرة أخرى قبل بدء الدردشة. + + + Cellular + خلوي + + + Chat + الدردشة + + + Chat already exists! + الدردشة موجودة بالفعل! + + + Chat will be deleted for you - this cannot be undone! + سيتم حذف الدردشة لديك - لا يمكن التراجع عن هذا! + + + Chat will be deleted for all members - this cannot be undone! + سيتم حذف الدردشة لجميع الأعضاء - لا يمكن التراجع عن هذا! + + + Change self-destruct passcode + تغيير رمز المرور التدمير الذاتي + + + Camera not available + الكاميرا غير متوفرة + + + Capacity exceeded - recipient did not receive previously sent messages. + تم تجاوز السعة - لم يتلق المُستلم الرسائل المُرسلة مسبقًا. + + + Change passcode + تغيير رمز المرور + + + Chat colors + ألوان الدردشة + + + Chat theme + سمة الدردشة + + + Business address + عنوان العمل التجاري + + + Business chats + دردشات العمل التجاري + + + Cancel migration + ألغِ الترحيل + + + Change chat profiles + غيّر ملفات تعريف الدردشة + + + Chat migrated! + رحّلت الدردشة! + + + Chat profile + ملف تعريف الدردشة + + + Contact deleted! + حُذفت جهة الاتصال! + + + Conditions of use + شروط الاستخدام + + + Connecting + جارِ الاتصال + + + Connect incognito + اتصال متخفي + + + Created at + أُنشئ في + + + Connect via contact address + الاتصال عبر عنوان جهة الاتصال + + + Connected servers + الخوادم المتصلة + + + standard end-to-end encryption + التعمية القياسية بين الطرفين + + + Delete up to 20 messages at once. + حذف ما يصل إلى 20 رسالة في آن واحد. + + + Connect to your friends faster. + تواصل مع أصدقائك بشكل أسرع. + + + Developer options + خيارات المطور + + + Connect to yourself? + اتصل بنفسك؟ + + + Connect via one-time link + اتصال عبر رابط لمرة واحدة + + + Connect to yourself? +This is your own SimpleX address! + اتصل بنفسك؟ +هذا هو عنوان SimpleX الخاص بك! + + + Connecting to contact, please wait or check later! + جارِ الاتصال بجهة الاتصال، يُرجى الانتظار أو التحقق لاحقًا! + + + Database upgrade + ترقية قاعدة البيانات + + + Create list + أنشئ قائمة + + + Create profile + إنشاء ملف تعريف + + + Creating archive link + جارِ إنشاء رابط الأرشيف + + + Details + التفاصيل + + + Customize theme + تخصيص السمة + + + Dark mode colors + ألوان الوضع الداكن + + + Delete and notify contact + حذف وإشعار جهة الاتصال + + + Deleted at: %@ + حُذفت في: %@ + + + Detailed statistics + إحصائيات مفصلة + + + you are observer + أنت المراقب + + + you + أنت + + + when IP hidden + عندما يكون IP مخفيًا + + + video + فيديو + + + Clear or delete group? + مسح أو حذف المجموعة؟ + + + Clear private notes? + مسح الملاحظات الخاصة؟ + + + Community guidelines violation + انتهاك إرشادات المجتمع + + + Connection not ready. + الاتصال غير جاهز. + + + Connection requires encryption renegotiation. + يتطلب الاتصال إعادة التفاوض على التعمية. + + + Contact is deleted. + حُذفت جهة الاتصال. + + + Contacts + جهات الاتصال + + + Create SimpleX address + أنشئ عنوان SimpleX + + + Current conditions text couldn't be loaded, you can review conditions via this link: + لا يمكن تحميل نص الشروط الحالية، يمكنك مراجعة الشروط عبر هذا الرابط: + + + Delete chat messages from your device. + احذف رسائل الدردشة من جهازك. + + + Delete or moderate up to 200 messages. + حذف أو إشراف ما يصل إلى 200 رسالة. + + + Delete profile + حذف ملف التعريف + + + Desktop devices + أجهزة سطح المكتب + + + set new profile picture + عيّن صورة تعريفية جديدة + + + weeks + أسابيع + + + Chunks uploaded + رُفع القطع + + + Color mode + وضع اللون + + + Created + أُنشئت + + + Current Passcode + رمز المرور الحالي + + + Custom time + وقت مخصّص + + + Debug delivery + تسليم التصحيح + + + Deleted + حُذفت + + + Delete file + حذف الملف + + + unknown status + حالة غير معروفة + + + unknown servers + خوادم غير معروفة + + + Connect to yourself? +This is your own one-time link! + اتصل بنفسك؟ +هذا هو الرابط الخاص بك لمرة واحدة! + + + Connect with %@ + الاتصال ب%@ + + + Connected desktop + سطح المكتب متصل + + + Connected to desktop + متصل بسطح المكتب + + + Conversation deleted! + حُذفت المحادثة! + + + Create a group using a random profile. + أنشئ مجموعة باستخدام ملف تعريف عشوائي. + + + Delete chat + احذف الدردشة + + + Delete chat profile + حذف ملف تعريف الدردشة + + + Delete chat? + حذف الدردشة؟ + + + Delete database from this device + احذف قاعدة البيانات من هذا الجهاز + + + Delivery + التوصيل + + + Delivery receipts are disabled! + إيصالات التسليم مُعطَّلة! + + + Connection terminated + انتهى الاتصال + + + Create file + إنشاء ملف + + + Create group + أنشئ مجموعة + + + Database IDs and Transport isolation option. + معرفات قاعدة البيانات وخيار عزل النقل. + + + Database downgrade + الرجوع إلى إصدار سابق من قاعدة البيانات + + + Delivery receipts! + إيصالات التسليم! + + + Desktop address + عنوان سطح المكتب + + + updated profile + حدّثت ملف التعريف + + + Connect to desktop + اتصل بسطح المكتب + + + Connecting to desktop + جار الاتصال بسطح المكتب + + + Completed + اكتملت + + + Connection notifications + إشعارات الاتصال + + + Connection and servers status. + حالة الاتصال والخوادم. + + + Continue + متابعة + + + Connections + الاتصالات + + + Content violates conditions of use + المحتوى ينتهك شروط الاستخدام + + + Corner + ركن + + + Creating link… + جارِ إنشاء الرابط… + + + Database ID: %d + معرّف قاعدة البيانات: %d + + + Decryption error + خطأ في فك التعمية + + + Delete report + احذف البلاغ + + + Delete without notification + احذف دون إشعار + + + Deleted at + حُذفت في + + + Clear group? + مسح المجموعة؟ + + + Compare file + قارن الملف + + + Connect automatically + اتصل تلقائيًا + + + Connection blocked + حُظر الاتصال + + + unprotected + غير محمي + + + Deletion errors + أخطاء الحذف + + + Conditions will be accepted for enabled operators after 30 days. + سيتم قبول الشروط للمُشغلين المفعّلين بعد 30 يومًا. + + + Connection security + أمان الاتصال + + + Contact will be deleted - this cannot be undone! + سيتم حذف جهة الاتصال - لا يمكن التراجع عن هذا! + + + Copy error + خطأ في النسخ + + + Create 1-time link + أنشئ رابط لمرة واحدة + + + Connected + متصل + + + Current profile + ملف التعريف الحالي + + + Customizable message shape. + شكل الرسالة قابل للتخصيص. + + + Chunks deleted + حُذفت القطع + + + Chinese and Spanish interface + الواجهة الصينية والاسبانية + + + Download + نزّل + + + Downloaded + نُزّلت + + + Downloaded files + الملفات التي نُزّلت + + + Don't show again + لا تُظهر مرة أخرى + + + Confirm contact deletion? + تأكيد حذف جهة الاتصال؟ + + + Confirm database upgrades + تأكيد ترقيات قاعدة البيانات + + + Download failed + فشل التنزيل + + + Download file + نزّل الملف + + + Downloading link details + جارِ تنزيل تفاصيل الرابط + + + Downloading archive + جارِ تنزيل الأرشيف + + + Don't enable + لا تُفعل + + + Confirm upload + أكّد الرفع + + + Chunks downloaded + نُزّلت القطع + + + Confirm Passcode + تأكيد رمز المرور + + + Confirm files from unknown servers. + تأكيد الملفات من خوادم غير معروفة. + + + Confirm network settings + أكّد إعدادات الشبكة + + + Confirm that you remember database passphrase to migrate it. + تأكد من أنك تتذكر عبارة مرور قاعدة البيانات لترحيلها. + + + Downgrade and open chat + الرجوع إلى إصدار سابق وفتح الدردشة + + + Don't miss important messages. + لا تفوت رسائل مهمة. + + + E2E encrypted notifications. + إشعارات مُشفرة بين الطرفين E2E + + + Download errors + أخطاء التنزيل + + + Download files + نزّل الملفات + + + Confirm password + تأكيد كلمة المرور + + + Enable self-destruct + تفعيل التدمير الذاتي + + + Enable (keep overrides) + تفعيل (الاحتفاظ بالتجاوزات) + + + Enable Flux + فعّل flux + + + Enable in direct chats (BETA)! + فعّل في الدردشات المباشرة (تجريبي)! + + + Enable for all + تفعيل للجميع + + + Enable lock + تفعيل القفل + + + Enable camera access + فعّل الوصول إلى الكاميرا + + + Enable self-destruct passcode + تفعيل رمز التدمير الذاتي + + + Can't message member + لا يمكن الاتصال بالعضو + + + Color chats with the new themes. + محادثات ملونة مع السمات الجديدة. + + + All chats will be removed from the list %@, and the list deleted. + ستتم إزالة جميع الدردشات من القائمة %@، وسيتم حذف القائمة. + + + 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)! + + + Choose _Migrate from another device_ on the new device and scan QR code. + اختر _الترحيل من جهاز آخر_ على الجهاز الجديد وامسح رمز الاستجابة السريعة. + + + Conditions will be accepted for the operator(s): **%@**. + سيتم قبول شروط المشغل (المشغلين): **%@**. + + + Conditions will be accepted on: %@. + سيتم قبول الشروط على: %@. + + + Confirmed + تم التأكيد + + + Connection is blocked by server operator: +%@ + تم حظر الاتصال من قبل مشغل الخادم: +%@ + + + Can't call member + لا يمكن الاتصال بالعضو + + + Chat already exists + الدردشة موجودة بالفعل + + + Check messages every 20 min. + تحقق من الرسائل كل 20 دقيقة. + + + Check messages when allowed. + تحقق من الرسائل عندما يُسمح بذلك. + + + Cannot forward message + لا يمكن إعادة توجيه الرسالة + + + Chat preferences were changed. + تم تغيير تفضيلات المحادثة. + + + Conditions are already accepted for these operator(s): **%@**. + الشروط مقبولة بالفعل لهذا المشغل (المشغلين): **%@**. + + + Conditions will be accepted for operator(s): **%@**. + سيتم قبول شروط المشغل (المشغلين): **%@**. + + + Conditions accepted on: %@. + الشروط المقبولة على: %@. + + + Conditions are accepted for the operator(s): **%@**. + يتم قبول شروط المشغل (المشغلين): **%@**. + + + Conditions will be automatically accepted for enabled operators on: %@. + سيتم قبول الشروط تلقائيًا للمشغلين الممكّنين على: %@. + + + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 + أنشئ ملفًا شخصيًا جديدًا في [تطبيق سطح المكتب](https://simplex.chat/downloads/). 💻 + + + Error adding server + خطأ في إضافة الخادم + + + Created at: %@ + تم الإنشاء في: %@ + + + Delete %lld messages of members? + حذف %lld الرسائل القديمة للأعضاء؟ + + + Disappearing message + رسالة اختفاء + + + Enabled + ممكّنة + + + Encrypted message: database migration error + رسالة مشفرة: خطأ في ترحيل قاعدة البيانات + + + Delete list? + Delete list? + + + Delivered even when Apple drops them. + يتم تسليمها حتى عندما تسقطها شركة Apple. + + + Destination server address of %@ is incompatible with forwarding server %@ settings. + عنوان خادم الوجهة %@ غير متوافق مع إعدادات خادم التوجيه %@. + + + Destination server version of %@ is incompatible with forwarding server %@. + إصدار خادم الوجهة لـ %@ غير متوافق مع خادم التوجيه %@. + + + Don't create address + لا تنشئ عنوان + + + Done + تم + + + Duration + المدة + + + Encrypt local files + تشفير الملفات المحلية + + + Encryption renegotiation in progress. + إعادة التفاوض على التشفير قيد التنفيذ. + + + Enter Passcode + أدخل رمز المرور + + + Enter passphrase + قم بأدخل عبارة المرور + + + Enter welcome message… + أدخل رسالة ترحيب… + + + Enter your name… + أدخل اسمك… + + + Error changing to incognito! + خطأ في التغيير إلى التصفح المتخفي! + + + Delete %lld messages? + حذف %lld رسائل؟ + + + Error aborting address change + خطأ في إجهاض تغيير العنوان + + + Disappears at + يختفي عند + + + Do not use credentials with proxy. + لا تستخدم بيانات الاعتماد مع البروكسي. + + + Error accepting conditions + خطأ في قبول الشروط + + + Enter password above to show! + أدخل كلمة المرور أعلاه للعرض! + + + Error changing connection profile + خطأ في تغيير ملف تعريف الاتصال + + + Desktop app version %@ is not compatible with this app. + إصدار تطبيق سطح المكتب %@ غير متوافق مع هذا التطبيق. + + + Encrypt stored files & media + تشفير الملفات والوسائط المخزنة + + + Enter this device name… + أدخل اسم الجهاز… + + + Enter welcome message… (optional) + أدخل رسالة ترحيب... (اختياري) + + + Correct name to %@? + الاسم الصحيح ل %@؟ + + + Delete member message? + حذف رسالة العضو؟ + + + Disable automatic message deletion? + تعطيل حذف الرسائل التلقائي؟ + + + Disable delete messages + تعطيل حذف الرسائل + + + Disable for all + تعطيل للجميع + + + Disabled + عاجز + + + Documents: + المستندات: + + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + باستخدامك SimpleX Chat، فإنك توافق على: +- إرسال محتوى قانوني فقط في المجموعات العامة. +- احترام المستخدمين الآخرين - ممنوع إرسال رسائل مزعجة. + + + Configure server operators + تكوين مشغلي الخادم + + + Enable Flux in Network & servers settings for better metadata privacy. + تمكين التدفق في إعدادات الشبكة والخوادم لتحسين خصوصية البيانات الوصفية. + + + Discover and join groups + اكتشف المجموعات وانضم إليها + + + Discover via local network + اكتشف عبر الشبكة المحلية + + + Enabled for + ممكّن ل + + + Encrypted message: app is stopped + رسالة مشفرة: تم إيقاف التطبيق + + + Enter group name… + أدخل اسم المجموعة… + + + Do NOT use private routing. + لا تستخدم التوجيه الخاص. + + + Encryption re-negotiation error + خطأ في إعادة تفاوض التشفير + + + Connection with desktop stopped + تم إيقاف الاتصال بسطح المكتب + + + Destination server error: %@ + خطأ خادم الوجهة: %@ + + + Do NOT send messages directly, even if your or destination server does not support private routing. + لا ترسل الرسائل بشكل مباشر، حتى لو كان خادمك أو خادم الوجهة لا يدعم التوجيه الخاص. + + + Direct messages between members are prohibited in this chat. + يُحظر إرسال الرسائل المباشرة بين الأعضاء في هذه الدردشة. + + + Disconnect desktop? + فصل سطح المكتب؟ + + + Disable (keep overrides) + تعطيل (الاحتفاظ بالتجاوزات) + + + Disappears at: %@ + يختفي عند: %@ + + + Do not send history to new members. + لا ترسل التاريخ إلى الأعضاء الجدد. + + + Encryption re-negotiation failed. + فشل إعادة التفاوض على التشفير. + @@ -3934,4 +5805,80 @@ SimpleX servers cannot see your profile. + + + + You can allow sharing in Privacy & Security / SimpleX Lock settings. + يمكنك السماح بالمشاركة في إعدادات الخصوصية والأمان / اعدادات "SimpleX Lock" + + + Keychain error + خطأ في Keychain + + + Invalid migration confirmation + تأكيد الترحيل غير صالح + + + %@ + %@ + + + Share + مشاركة + + + Incompatible database version + إصدار قاعدة بيانات غير متوافق + + + File error + خطأ في الملف + + + Database downgrade required + مطلوب الرجوع إلى إصدار سابق من قاعدة البيانات‎ + + + Database encrypted! + قاعدة البيانات مُعمّاة! + + + Wrong database passphrase + عبارة مرور قاعدة بيانات خاطئة + + + Selected chat preferences prohibit this message. + تفضيلات الدردشة المحدّدة تحظر هذه الرسالة. + + + Database error + خطأ في قاعدة البيانات + + + Database passphrase is required to open chat. + عبارة مرور قاعدة البيانات مطلوبة لفتح الدردشة. + + + Error: %@ + خطأ: %@ + + + Cancel + إلغاء + + + Large file! + الملف كبير! + + + + + + + From: %@ + من: %@ + + + 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 419f0ae864..776199ac1f 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (може да се копира) @@ -127,6 +100,14 @@ %@ е потвърдено No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ качено @@ -137,6 +118,11 @@ %@ иска да се свърже! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ и %lld членове @@ -157,11 +143,31 @@ %d дни time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d часа time interval + + %d messages not forwarded + alert title + %d min %d мин. @@ -177,6 +183,10 @@ %d сек. time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d пропуснато(и) съобщение(я) @@ -247,11 +257,6 @@ %lld нови езици на интерфейса No comment provided by engineer. - - %lld second(s) - %lld секунда(и) - No comment provided by engineer. - %lld seconds %lld секунди @@ -302,11 +307,6 @@ %u пропуснати съобщения. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (ново) @@ -317,19 +317,9 @@ (това устройство v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Добави контакт**: за създаване на нов линк или свързване чрез получен линк за връзка. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Добави нов контакт**: за да създадете своя еднократен QR код или линк за вашия контакт. + + **Create 1-time link**: to create and share a new invitation link. + **Добави контакт**: за създаване на нов линк. No comment provided by engineer. @@ -337,13 +327,13 @@ **Създай група**: за създаване на нова група. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението). No comment provided by engineer. @@ -357,11 +347,15 @@ **Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Внимание**: Незабавните push известия изискват парола, запазена в Keychain. @@ -387,11 +381,6 @@ \*удебелен* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +417,6 @@ - история на редактиране. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 сек @@ -446,7 +430,8 @@ 1 day 1 ден - time interval + delete after time +time interval 1 hour @@ -461,12 +446,27 @@ 1 month 1 месец - time interval + delete after time +time interval 1 week 1 седмица - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + Еднократен линк + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. 5 minutes @@ -483,11 +483,6 @@ 30 секунди No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,31 +532,32 @@ Откажи смяна на адрес? No comment provided by engineer. - - About SimpleX - За SimpleX - No comment provided by engineer. - About SimpleX Chat За SimpleX Chat No comment provided by engineer. - - About SimpleX address - Повече за SimpleX адреса + + About operators + За операторите No comment provided by engineer. Accent + Акцент No comment provided by engineer. Accept Приеми accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Приеми условията + No comment provided by engineer. Accept connection request? @@ -577,18 +573,30 @@ Accept incognito Приеми инкогнито accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Приети условия + No comment provided by engineer. Acknowledged + Потвърден No comment provided by engineer. Acknowledgement errors + Грешки при потвърждението No comment provided by engineer. + + Active + token status text + Active connections + Активни връзки No comment provided by engineer. @@ -596,14 +604,13 @@ Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. No comment provided by engineer. - - Add contact - Добави контакт + + Add friends + Добави приятели No comment provided by engineer. - - Add preset servers - Добави предварително зададени сървъри + + Add list No comment provided by engineer. @@ -621,26 +628,53 @@ Добави сървъри чрез сканиране на QR кодове. No comment provided by engineer. + + Add team members + Добави членове на екипа + No comment provided by engineer. + Add to another device Добави към друго устройство No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message Добави съобщение при посрещане No comment provided by engineer. + + Add your team members to the conversations. + Добавете членовете на вашия екип към разговорите. + No comment provided by engineer. + + + Added media & file servers + Добавени медийни и файлови сървъри + No comment provided by engineer. + + + Added message servers + Добавени сървъри за съобщения + No comment provided by engineer. + Additional accent + Допълнителен акцент No comment provided by engineer. Additional accent 2 + Допълнителен акцент 2 No comment provided by engineer. Additional secondary + Допълнителен вторичен No comment provided by engineer. @@ -653,6 +687,16 @@ Промяната на адреса ще бъде прекъсната. Ще се използва старият адрес за получаване. No comment provided by engineer. + + Address or 1-time link? + Адрес или еднократен линк? + No comment provided by engineer. + + + Address settings + Настройки на адреса + No comment provided by engineer. + Admins can block a member for all. Администраторите могат да блокират член за всички. @@ -670,6 +714,11 @@ Advanced settings + Разширени настройки + No comment provided by engineer. + + + All No comment provided by engineer. @@ -682,13 +731,18 @@ Всички чатове и съобщения ще бъдат изтрити - това не може да бъде отменено! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. Всички данни се изтриват при въвеждане. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. + Всички данни се съхраняват поверително на вашето устройство. No comment provided by engineer. @@ -696,6 +750,10 @@ Всички членове на групата ще останат свързани. 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! Всички съобщения ще бъдат изтрити - това не може да бъде отменено! @@ -713,6 +771,15 @@ All profiles + Всички профили + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -742,6 +809,7 @@ Allow calls? + Позволи обаждания? No comment provided by engineer. @@ -751,6 +819,7 @@ Allow downgrade + Позволи понижаване No comment provided by engineer. @@ -780,6 +849,7 @@ Allow sharing + Позволи споделяне No comment provided by engineer. @@ -787,6 +857,10 @@ Позволи необратимо изтриване на изпратените съобщения. (24 часа) No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. Разрешаване на изпращане на SimpleX линкове. @@ -854,6 +928,7 @@ Always use private routing. + Винаги използвай поверително рутиране. No comment provided by engineer. @@ -866,11 +941,20 @@ Създаен беше празен профил за чат с предоставеното име и приложението се отвари както обикновено. No comment provided by engineer. + + Another reason + report reason + Answer call Отговор на повикване No comment provided by engineer. + + Anybody can host servers. + Протокол и код с отворен код – всеки може да оперира собствени сървъри. + No comment provided by engineer. + App build: %@ Компилация на приложението: %@ @@ -886,6 +970,10 @@ Приложението криптира нови локални файлове (с изключение на видеоклипове). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon Икона на приложението @@ -901,6 +989,11 @@ Кода за достъп до приложение се заменя с код за самоунищожение. No comment provided by engineer. + + App session + Сесия на приложението + No comment provided by engineer. + App version Версия на приложението @@ -923,6 +1016,19 @@ Apply to + Приложи към + No comment provided by engineer. + + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? No comment provided by engineer. @@ -932,10 +1038,24 @@ Archive contacts to chat later. + Архивирайте контактите, за да разговаряте по-късно. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts + Архивирани контакти No comment provided by engineer. @@ -1003,6 +1123,11 @@ Автоматично приемане на изображения No comment provided by engineer. + + Auto-accept settings + Автоматично приемане на настройки + alert title + Back Назад @@ -1010,6 +1135,7 @@ Background + Фон No comment provided by engineer. @@ -1027,11 +1153,25 @@ Лош хеш на съобщението No comment provided by engineer. + + Better calls + По-добри обаждания + No comment provided by engineer. + Better groups По-добри групи No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + По-добри дати на съобщението. + No comment provided by engineer. + Better messages По-добри съобщения @@ -1039,10 +1179,31 @@ Better networking + Подобрена мрежа + No comment provided by engineer. + + + Better notifications + Подобрени известия + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + По-добра сигурност ✅ + No comment provided by engineer. + + + Better user experience + Подобрен интерфейс No comment provided by engineer. Black + Черна No comment provided by engineer. @@ -1082,10 +1243,12 @@ Blur for better privacy. + Размазване за по-добра поверителност. No comment provided by engineer. Blur media + Размазване на медия No comment provided by engineer. @@ -1118,11 +1281,31 @@ Български, финландски, тайландски и украински - благодарение на потребителите и [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Бизнес адрес + No comment provided by engineer. + + + Business chats + Бизнес чатове + No comment provided by engineer. + + + Businesses + 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: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Разговорът вече приключи! @@ -1135,6 +1318,7 @@ Calls prohibited! + Обажданията са забранени! No comment provided by engineer. @@ -1144,10 +1328,12 @@ Can't call contact + Обаждането на контакта не е позволено No comment provided by engineer. Can't call member + Обаждането на члена не е позволено No comment provided by engineer. @@ -1167,7 +1353,8 @@ Cancel Отказ - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1186,10 +1373,11 @@ Cannot receive file Файлът не може да бъде получен - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. + Капацитетът е надвишен - получателят не е получил предишно изпратените съобщения. snd error text @@ -1202,6 +1390,15 @@ Промени No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + Промени чат профилите + authentication reason + Change database passphrase? Промяна на паролата на базата данни? @@ -1246,11 +1443,18 @@ Change self-destruct passcode Промени кода за достъп за самоунищожение authentication reason - set passcode view +set passcode view - - Chat archive - Архив на чата + + Chat + No comment provided by engineer. + + + Chat already exists + No comment provided by engineer. + + + Chat already exists! No comment provided by engineer. @@ -1264,7 +1468,7 @@ Chat database - База данни за чата + База данни No comment provided by engineer. @@ -1278,7 +1482,7 @@ Chat database imported - Базата данни на чат е импортирана + Базата данни на е импортирана No comment provided by engineer. @@ -1310,19 +1514,44 @@ Чат настройки No comment provided by engineer. + + Chat preferences were changed. + alert message + + + Chat profile + Потребителски профил + No comment provided by engineer. + Chat theme No comment provided by engineer. + + 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. + Chats Чатове No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Проверете адреса на сървъра и опитайте отново. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1371,6 +1600,14 @@ Изчисти разговора? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? Изчистване на лични бележки? @@ -1389,6 +1626,10 @@ Color mode No comment provided by engineer. + + Community guidelines violation + report reason + Compare file Сравни файл @@ -1403,13 +1644,41 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Конфигурирай ICE сървъри No comment provided by engineer. - - Configured %@ servers + + Configure server operators No comment provided by engineer. @@ -1460,6 +1729,10 @@ Потвърди качването No comment provided by engineer. + + Confirmed + token status text + Connect Свързване @@ -1573,6 +1846,10 @@ This is your own one-time link! Connection and servers status. No comment provided by engineer. + + Connection blocked + No comment provided by engineer. + Connection error Грешка при свързване @@ -1583,6 +1860,15 @@ This is your own one-time link! Грешка при свързване (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications No comment provided by engineer. @@ -1592,6 +1878,14 @@ This is your own one-time link! Заявката за връзка е изпратена! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + No comment provided by engineer. + Connection terminated Връзката е прекратена @@ -1662,6 +1956,10 @@ This is your own one-time link! Контактите могат да маркират съобщения за изтриване; ще можете да ги разглеждате. No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue Продължи @@ -1685,6 +1983,10 @@ This is your own one-time link! Версия на ядрото: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Поправи име на %@? @@ -1692,32 +1994,32 @@ This is your own one-time link! Create - Създай + Създаване + No comment provided by engineer. + + + Create 1-time link + Създаване на еднократна препратка No comment provided by engineer. Create SimpleX address - Създай SimpleX адрес + Създаване на адрес в SimpleX No comment provided by engineer. Create a group using a random profile. - Създай група с автоматично генериран профилл. - No comment provided by engineer. - - - Create an address to let people connect with you. - Създайте адрес, за да позволите на хората да се свързват с вас. + Създаване група с автоматично създаден профил. No comment provided by engineer. Create file - Създай файл + Създаване на файл server test step Create group - Създай група + Създаване на група No comment provided by engineer. @@ -1730,6 +2032,10 @@ This is your own one-time link! Създай линк No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Създайте нов профил в [настолното приложение](https://simplex.chat/downloads/). 💻 @@ -1769,11 +2075,6 @@ This is your own one-time link! Създаден на: %@ copied message info - - Created on %@ - Създаден на %@ - No comment provided by engineer. - Creating archive link Създаване на архивен линк @@ -1789,6 +2090,10 @@ This is your own one-time link! Текущ kод за достъп No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Текуща парола… @@ -1808,6 +2113,10 @@ This is your own one-time link! Персонализирано време No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -1936,8 +2245,8 @@ This is your own one-time link! Delete Изтрий - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -1973,14 +2282,12 @@ This is your own one-time link! Изтрий и уведоми контакт No comment provided by engineer. - - Delete archive - Изтрий архив + + Delete chat No comment provided by engineer. - - Delete chat archive? - Изтриване на архива на чата? + + Delete chat messages from your device. No comment provided by engineer. @@ -1993,6 +2300,10 @@ This is your own one-time link! Изтриване на чат профила? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Изтрий връзката @@ -2067,6 +2378,10 @@ This is your own one-time link! Изтрий линк? No comment provided by engineer. + + Delete list? + alert title + Delete member message? Изтрий съобщението на члена? @@ -2080,7 +2395,7 @@ This is your own one-time link! Delete messages Изтрий съобщенията - No comment provided by engineer. + alert button Delete messages after @@ -2097,6 +2412,10 @@ This is your own one-time link! Изтрий старата база данни? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Изтрий предстоящата връзка? @@ -2112,6 +2431,10 @@ This is your own one-time link! Изтрий опашка server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. No comment provided by engineer. @@ -2143,6 +2466,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Доставка @@ -2237,8 +2564,12 @@ This is your own one-time link! Лични съобщения chat feature - - Direct messages between members are prohibited in this group. + + 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. @@ -2252,6 +2583,14 @@ This is your own one-time link! Деактивирай SimpleX заключване authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all Деактивиране за всички @@ -2276,8 +2615,8 @@ This is your own one-time link! Изчезващите съобщения са забранени в този чат. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Изчезващите съобщения са забранени в тази група. No comment provided by engineer. @@ -2334,6 +2673,14 @@ This is your own one-time link! Не изпращай история на нови членове. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address Не създавай адрес @@ -2344,11 +2691,19 @@ This is your own one-time link! Не активирай No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again Не показвай отново No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat Понижи версията и отвори чата @@ -2357,7 +2712,8 @@ This is your own one-time link! Download Изтегли - chat item action + alert button +chat item action Download errors @@ -2373,6 +2729,10 @@ This is your own one-time link! Свали файл server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2401,6 +2761,10 @@ This is your own one-time link! Продължителност No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Редактирай @@ -2421,6 +2785,10 @@ This is your own one-time link! Активиране (запазване на промените) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Активирай SimpleX заключване @@ -2434,7 +2802,7 @@ This is your own one-time link! Enable automatic message deletion? Активиране на автоматично изтриване на съобщения? - No comment provided by engineer. + alert title Enable camera access @@ -2560,6 +2928,10 @@ This is your own one-time link! Неуспешно повторно договаряне на криптирането. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Въведете kодa за достъп @@ -2625,26 +2997,33 @@ This is your own one-time link! Грешка при отказване на промяна на адреса No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Грешка при приемане на заявка за контакт No comment provided by engineer. - - Error accessing database file - Грешка при достъпа до файла с базата данни - No comment provided by engineer. - Error adding member(s) Грешка при добавяне на член(ове) No comment provided by engineer. + + Error adding server + alert title + Error changing address Грешка при промяна на адреса No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Грешка при промяна на ролята @@ -2655,6 +3034,14 @@ This is your own one-time link! Грешка при промяна на настройката No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2674,6 +3061,10 @@ This is your own one-time link! Грешка при създаване на групов линк No comment provided by engineer. + + Error creating list + alert title + Error creating member contact Грешка при създаване на контакт с член @@ -2689,6 +3080,10 @@ This is your own one-time link! Грешка при създаване на профил! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file Грешка при декриптирането на файла @@ -2696,7 +3091,7 @@ This is your own one-time link! Error deleting chat database - Грешка при изтриване на чат базата данни + Грешка при изтриване на базата данни No comment provided by engineer. @@ -2751,7 +3146,7 @@ This is your own one-time link! Error exporting chat database - Грешка при експортиране на чат базата данни + Грешка при експортиране на базата данни No comment provided by engineer. @@ -2760,7 +3155,7 @@ This is your own one-time link! Error importing chat database - Грешка при импортиране на чат базата данни + Грешка при импортиране на базата данни No comment provided by engineer. @@ -2768,9 +3163,12 @@ This is your own one-time link! Грешка при присъединяване към група No comment provided by engineer. - - Error loading %@ servers - Грешка при зареждане на %@ сървъри + + Error loading servers + alert title + + + Error migrating settings No comment provided by engineer. @@ -2781,7 +3179,7 @@ This is your own one-time link! Error receiving file Грешка при получаване на файл - No comment provided by engineer. + alert title Error reconnecting server @@ -2791,25 +3189,32 @@ This is your own one-time link! Error reconnecting servers No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member Грешка при отстраняване на член No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Грешка при запазване на %@ сървъра - No comment provided by engineer. - Error saving ICE servers Грешка при запазване на ICE сървърите No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile Грешка при запазване на профила на групата @@ -2825,6 +3230,10 @@ This is your own one-time link! Грешка при запазване на парола в Кeychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Грешка при запазване на настройките @@ -2870,16 +3279,24 @@ This is your own one-time link! Грешка при спиране на чата No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Грешка при смяна на профил! - No comment provided by engineer. + alertTitle Error synchronizing connection Грешка при синхронизиране на връзката No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link Грешка при актуализиране на груповия линк @@ -2890,6 +3307,10 @@ This is your own one-time link! Грешка при актуализиране на съобщението No comment provided by engineer. + + Error updating server + alert title + Error updating settings Грешка при актуализиране на настройките @@ -2918,8 +3339,9 @@ This is your own one-time link! Error: %@ Грешка: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2935,6 +3357,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Дори когато е деактивиран в разговора. @@ -2950,6 +3376,10 @@ This is your own one-time link! Разшири chat item action + + Expired + token status text + Export database Експортирай база данни @@ -2989,19 +3419,41 @@ This is your own one-time link! Бързо и без чакане, докато подателят е онлайн! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. По-бързо присъединяване и по-надеждни съобщения. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite Любим swipe action + + Favorites + No comment provided by engineer. + File error - No comment provided by engineer. + file error alert title + + + File errors: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3053,8 +3505,8 @@ This is your own one-time link! Файлове и медия chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Файловете и медията са забранени в тази група. No comment provided by engineer. @@ -3123,21 +3575,61 @@ This is your own one-time link! Поправката не се поддържа от члена на групата No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + servers error + For console За конзолата No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Препрати chat item action + + Forward %d message(s)? + alert title + Forward and save messages Препращане и запазване на съобщения No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Препратено @@ -3148,6 +3640,10 @@ This is your own one-time link! Препратено от No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3190,11 +3686,6 @@ Error: %2$@ Пълно име (незадължително) No comment provided by engineer. - - Full name: - Пълно име: - No comment provided by engineer. - Fully decentralized – visible only to members. Напълно децентрализирана – видима е само за членовете. @@ -3215,6 +3706,10 @@ Error: %2$@ GIF файлове и стикери No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! message preview @@ -3278,41 +3773,6 @@ Error: %2$@ Групови линкове No comment provided by engineer. - - Group members can add message reactions. - Членовете на групата могат да добавят реакции към съобщенията. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) - No comment provided by engineer. - - - Group members can send SimpleX links. - Членовете на групата могат да изпращат SimpleX линкове. - No comment provided by engineer. - - - Group members can send direct messages. - Членовете на групата могат да изпращат лични съобщения. - No comment provided by engineer. - - - Group members can send disappearing messages. - Членовете на групата могат да изпращат изчезващи съобщения. - No comment provided by engineer. - - - Group members can send files and media. - Членовете на групата могат да изпращат файлове и медия. - No comment provided by engineer. - - - Group members can send voice messages. - Членовете на групата могат да изпращат гласови съобщения. - No comment provided by engineer. - Group message: Групово съобщение: @@ -3353,11 +3813,19 @@ Error: %2$@ Групата ще бъде изтрита за вас - това не може да бъде отменено! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Помощ No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Скрит @@ -3408,10 +3876,17 @@ Error: %2$@ Как работи SimpleX No comment provided by engineer. + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + No comment provided by engineer. + How it works - Как работи - No comment provided by engineer. + alert button How to @@ -3438,6 +3913,10 @@ Error: %2$@ ICE сървъри (по един на ред) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Ако не можете да се срещнете лично, покажете QR код във видеоразговора или споделете линка. @@ -3478,8 +3957,8 @@ Error: %2$@ Веднага No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Защитен от спам и злоупотреби No comment provided by engineer. @@ -3490,7 +3969,7 @@ Error: %2$@ Import chat database? - Импортиране на чат база данни? + Импортиране на база данни? No comment provided by engineer. @@ -3512,6 +3991,11 @@ Error: %2$@ Импортиране на архив No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Подобрена доставка на съобщения @@ -3542,6 +4026,14 @@ Error: %2$@ Звуци по време на разговор No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Инкогнито @@ -3612,6 +4104,11 @@ Error: %2$@ Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Мигновено + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3619,11 +4116,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Мигновено - No comment provided by engineer. - Interface Интерфейс @@ -3633,6 +4125,26 @@ Error: %2$@ Interface colors No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code Невалиден QR код @@ -3671,7 +4183,7 @@ Error: %2$@ Invalid server address! Невалиден адрес на сървъра! - No comment provided by engineer. + alert title Invalid status @@ -3693,6 +4205,10 @@ Error: %2$@ Покани членове No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Покани в групата @@ -3708,8 +4224,8 @@ Error: %2$@ Необратимото изтриване на съобщения е забранено в този чат. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Необратимото изтриване на съобщения е забранено в тази група. No comment provided by engineer. @@ -3798,7 +4314,7 @@ This is your link for group %@! Keep Запази - No comment provided by engineer. + alert action Keep conversation @@ -3812,7 +4328,7 @@ This is your link for group %@! Keep unused invitation? Запази неизползваната покана за връзка? - No comment provided by engineer. + alert title Keep your connections @@ -3849,6 +4365,14 @@ This is your link for group %@! Напусни swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Напусни групата @@ -3889,6 +4413,18 @@ This is your link for group %@! Запомнени настолни устройства No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Съобщение на живо! @@ -3899,11 +4435,6 @@ This is your link for group %@! Съобщения на живо No comment provided by engineer. - - Local - Локално - No comment provided by engineer. - Local name Локално име @@ -3924,11 +4455,6 @@ This is your link for group %@! Режим на заключване No comment provided by engineer. - - Make a private connection - Добави поверителна връзка - No comment provided by engineer. - Make one message disappear Накарайте едно съобщение да изчезне @@ -3939,21 +4465,11 @@ This is your link for group %@! Направи профила поверителен! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Уверете се, че %@ сървърните адреси са в правилен формат, разделени на редове и не се дублират (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Уверете се, че адресите на WebRTC ICE сървъра са в правилен формат, разделени на редове и не са дублирани. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Много хора попитаха: *ако SimpleX няма потребителски идентификатори, как може да доставя съобщения?* - No comment provided by engineer. - Mark deleted for everyone Маркирай като изтрито за всички @@ -3996,6 +4512,14 @@ This is your link for group %@! Member inactive item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Ролята на члена ще бъде променена на "%@". Всички членове на групата ще бъдат уведомени. @@ -4006,11 +4530,58 @@ This is your link for group %@! Ролята на члена ще бъде променена на "%@". Членът ще получи нова покана. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Членът ще бъде премахнат от групата - това не може да бъде отменено! No comment provided by engineer. + + Members can add message reactions. + Членовете на групата могат да добавят реакции към съобщенията. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Членовете на групата могат да изпращат SimpleX линкове. + No comment provided by engineer. + + + Members can send direct messages. + Членовете на групата могат да изпращат лични съобщения. + No comment provided by engineer. + + + Members can send disappearing messages. + Членовете на групата могат да изпращат изчезващи съобщения. + No comment provided by engineer. + + + Members can send files and media. + Членовете на групата могат да изпращат файлове и медия. + No comment provided by engineer. + + + Members can send voice messages. + Членовете на групата могат да изпращат гласови съобщения. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus No comment provided by engineer. @@ -4056,8 +4627,8 @@ This is your link for group %@! Реакциите на съобщения са забранени в този чат. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Реакциите на съобщения са забранени в тази група. No comment provided by engineer. @@ -4069,6 +4640,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Източникът на съобщението остава скрит. @@ -4107,6 +4682,10 @@ This is your link for group %@! Съобщенията от %@ ще бъдат показани! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received No comment provided by engineer. @@ -4115,6 +4694,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Съобщенията, файловете и разговорите са защитени чрез **криптиране от край до край** с перфектна секретност при препращане, правдоподобно опровержение и възстановяване при взлом. @@ -4180,9 +4763,9 @@ This is your link for group %@! Миграцията е завършена No comment provided by engineer. - - Migrations: %@ - Миграции: %@ + + Migrations: + Миграции: No comment provided by engineer. @@ -4200,6 +4783,10 @@ This is your link for group %@! Модерирано в: %@ copied message info + + More + swipe action + More improvements are coming soon! Очаквайте скоро още подобрения! @@ -4210,6 +4797,10 @@ This is your link for group %@! По-надеждна мрежова връзка. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Най-вероятно тази връзка е изтрита. @@ -4223,7 +4814,11 @@ This is your link for group %@! Mute Без звук - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4245,6 +4840,10 @@ This is your link for group %@! Мрежова връзка No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4254,6 +4853,10 @@ This is your link for group %@! Управление на мрежата No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Мрежови настройки @@ -4264,11 +4867,23 @@ This is your link for group %@! Състояние на мрежата No comment provided by engineer. + + New + token status text + New Passcode Нов kод за достъп No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + No comment provided by engineer. + New chat Нов чат @@ -4288,11 +4903,6 @@ This is your link for group %@! Нов контакт: notification - - New database archive - Нов архив на база данни - No comment provided by engineer. - New desktop app! Ново настолно приложение! @@ -4303,6 +4913,10 @@ This is your link for group %@! Ново име No comment provided by engineer. + + New events + notification + New in %@ Ново в %@ @@ -4327,6 +4941,10 @@ This is your link for group %@! Нова парола… No comment provided by engineer. + + New server + No comment provided by engineer. + No Не @@ -4337,6 +4955,18 @@ This is your link for group %@! Приложението няма kод за достъп Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Няма избрани контакти @@ -4380,30 +5010,92 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection Няма мрежова връзка No comment provided by engineer. + + No permission to record speech + No comment provided by engineer. + + + No permission to record video + No comment provided by engineer. + No permission to record voice message Няма разрешение за запис на гласово съобщение No comment provided by engineer. + + No push server + Локално + No comment provided by engineer. + No received or sent files Няма получени или изпратени файлове No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. + No comment provided by engineer. + Not compatible! Несъвместим! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Известия @@ -4414,6 +5106,18 @@ This is your link for group %@! Известията са деактивирани! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4436,18 +5140,13 @@ This is your link for group %@! Ok Ок - No comment provided by engineer. + alert button Old database Стара база данни No comment provided by engineer. - - Old database archive - Стар архив на база данни - No comment provided by engineer. - One-time invitation link Линк за еднократна покана @@ -4472,8 +5171,12 @@ Requires compatible VPN. Няма се използват Onion хостове. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**. No comment provided by engineer. @@ -4496,6 +5199,14 @@ Requires compatible VPN. Само собствениците на групата могат да активират гласови съобщения. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Само вие можете да добавяте реакции на съобщенията. @@ -4549,13 +5260,17 @@ Requires compatible VPN. Open Отвори - No comment provided by engineer. + alert action Open Settings Отвори настройки No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Отвори чат @@ -4566,35 +5281,41 @@ Requires compatible VPN. Отвори конзолата authentication reason + + Open conditions + No comment provided by engineer. + Open group Отвори група No comment provided by engineer. + + Open link? + alert title + Open migration to another device Отвори миграцията към друго устройство authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Отвори потребителските профили - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Протокол и код с отворен код – всеки може да оперира собствени сървъри. - No comment provided by engineer. - Opening app… Приложението се отваря… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link Или постави архивен линк @@ -4615,14 +5336,23 @@ Requires compatible VPN. Или покажи този код No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other Други No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + alert message PING count @@ -4659,6 +5389,10 @@ Requires compatible VPN. Кодът за достъп е зададен! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Парола за показване @@ -4693,13 +5427,8 @@ Requires compatible VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - Хората могат да се свържат с вас само чрез ликовете, които споделяте. - No comment provided by engineer. - - - Periodically + + Periodic Периодично No comment provided by engineer. @@ -4798,11 +5527,27 @@ Error: %@ Моля, съхранявайте паролата на сигурно място, НЯМА да можете да я промените, ако я загубите. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Полски интерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен @@ -4813,16 +5558,15 @@ Error: %@ Запазете последната чернова на съобщението с прикачени файлове. No comment provided by engineer. - - Preset server - Предварително зададен сървър - No comment provided by engineer. - Preset server address Предварително зададен адрес на сървъра No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Визуализация @@ -4837,16 +5581,32 @@ Error: %@ Поверителност и сигурност No comment provided by engineer. + + Privacy for your customers. + 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 Поверителни имена на файлове No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing No comment provided by engineer. @@ -4883,16 +5643,6 @@ Error: %@ Профилни изображения No comment provided by engineer. - - Profile name - Име на профила - No comment provided by engineer. - - - Profile name: - Име на профила: - No comment provided by engineer. - Profile password Профилна парола @@ -4905,7 +5655,7 @@ Error: %@ Profile update will be sent to your contacts. Актуализацията на профила ще бъде изпратена до вашите контакти. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4927,6 +5677,10 @@ Error: %@ Забрани реакциите на съобщенията. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. Забранете изпращането на SimpleX линкове. @@ -4989,6 +5743,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push известия @@ -5028,26 +5786,21 @@ Enable in *Network & servers* settings. Прочетете още No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Прочетете повече в нашето хранилище в GitHub. - 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). @@ -5169,11 +5922,23 @@ Enable in *Network & servers* settings. Намалена консумация на батерията No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Отхвърляне reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5200,6 +5965,10 @@ Enable in *Network & servers* settings. Премахване No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5264,6 +6033,46 @@ Enable in *Network & servers* settings. Отговори chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Задължително @@ -5344,6 +6153,10 @@ Enable in *Network & servers* settings. Покажи chat item action + + Review conditions + No comment provided by engineer. + Revoke Отзови @@ -5373,6 +6186,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5385,17 +6202,18 @@ Enable in *Network & servers* settings. Save Запази - chat item action + alert button +chat item action Save (and notify contacts) Запази (и уведоми контактите) - No comment provided by engineer. + alert button Save and notify contact Запази и уведоми контакта - No comment provided by engineer. + alert button Save and notify group members @@ -5411,21 +6229,15 @@ Enable in *Network & servers* settings. Запази и актуализирай профила на групата No comment provided by engineer. - - Save archive - Запази архив - No comment provided by engineer. - - - Save auto-accept settings - Запази настройките за автоматично приемане - No comment provided by engineer. - Save group profile Запази профила на групата No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Запази паролата и отвори чата @@ -5439,7 +6251,7 @@ Enable in *Network & servers* settings. Save preferences? Запази настройките? - No comment provided by engineer. + alert title Save profile password @@ -5454,18 +6266,17 @@ Enable in *Network & servers* settings. Save servers? Запази сървърите? - No comment provided by engineer. - - - Save settings? - Запази настройките? - No comment provided by engineer. + alert title Save welcome message? Запази съобщението при посрещане? No comment provided by engineer. + + Save your profile? + alert title + Saved Запазено @@ -5486,6 +6297,10 @@ Enable in *Network & servers* settings. Запазено съобщение message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5562,6 +6377,10 @@ Enable in *Network & servers* settings. Избери chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5646,9 +6465,8 @@ Enable in *Network & servers* settings. Изпращай известия No comment provided by engineer. - - Send notifications: - Изпратени известия: + + Send private reports No comment provided by engineer. @@ -5674,7 +6492,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Подателят отмени прехвърлянето на файла. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5766,6 +6584,14 @@ Enable in *Network & servers* settings. Sent via proxy No comment provided by engineer. + + Server + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5778,6 +6604,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Сървърът изисква оторизация за създаване на опашки, проверете паролата @@ -5828,6 +6666,10 @@ Enable in *Network & servers* settings. Задай 1 ден No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Задай име на контакт… @@ -5847,6 +6689,10 @@ Enable in *Network & servers* settings. Задайте го вместо системната идентификация. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Задай kод за достъп @@ -5877,6 +6723,10 @@ Enable in *Network & servers* settings. Настройки No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Променете формата на профилните изображения @@ -5885,22 +6735,35 @@ Enable in *Network & servers* settings. Share Сподели - chat item action + alert action +chat item action Share 1-time link Сподели еднократен линк No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Сподели адрес No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Сподели адреса с контактите? - No comment provided by engineer. + alert title Share from other apps. @@ -5911,6 +6774,10 @@ Enable in *Network & servers* settings. Сподели линк No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Сподели този еднократен линк за връзка @@ -5925,6 +6792,10 @@ Enable in *Network & servers* settings. Сподели с контактите No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Покажи QR код @@ -5976,6 +6847,10 @@ Enable in *Network & servers* settings. SimpleX Адрес No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Сигурността на SimpleX Chat беше одитирана от Trail of Bits. @@ -6006,6 +6881,18 @@ Enable in *Network & servers* settings. SimpleX адрес No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX адрес за контакт @@ -6026,8 +6913,8 @@ Enable in *Network & servers* settings. SimpleX линкове chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. SimpleX линкове са забранени в тази група. No comment provided by engineer. @@ -6041,6 +6928,10 @@ Enable in *Network & servers* settings. Еднократна покана за SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Опростен режим инкогнито @@ -6069,6 +6960,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6082,11 +6977,21 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Някой notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. Квадрат, кръг или нещо между тях. @@ -6130,11 +7035,6 @@ Enable in *Network & servers* settings. Спри чата No comment provided by engineer. - - Stop chat to enable database actions - Спрете чата, за да активирате действията с базата данни - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Спрете чата, за да експортирате, импортирате или изтриете чат базата данни. Няма да можете да получавате и изпращате съобщения, докато чатът е спрян. @@ -6163,18 +7063,22 @@ Enable in *Network & servers* settings. Stop sharing Спри споделянето - No comment provided by engineer. + alert action Stop sharing address? Спри споделянето на адреса? - No comment provided by engineer. + alert title Stopping chat Спиране на чата No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong blur media @@ -6201,6 +7105,14 @@ Enable in *Network & servers* settings. Подкрепете SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Системен @@ -6220,6 +7132,10 @@ Enable in *Network & servers* settings. Времето на изчакване за установяване на TCP връзка No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6235,11 +7151,19 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Направи снимка No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Докосни бутона @@ -6277,13 +7201,17 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + file error alert title Test failed at step %@. Тестът е неуспешен на стъпка %@. server test failure + + Test notifications + No comment provided by engineer. + Test server Тествай сървър @@ -6297,7 +7225,7 @@ Enable in *Network & servers* settings. Tests failed! Тестовете са неуспешни! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6314,11 +7242,6 @@ Enable in *Network & servers* settings. Благодарение на потребителите – допринесете през Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6331,6 +7254,10 @@ It can happen because of some bug or when the connection is compromised.Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6345,6 +7272,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Връзката, която приехте, ще бъде отказана! @@ -6365,6 +7296,11 @@ It can happen because of some bug or when the connection is compromised.Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване! No comment provided by engineer. + + The future of messaging + Ново поколение поверителни съобщения + No comment provided by engineer. + The hash of the previous message is different. Хешът на предишното съобщение е различен. @@ -6388,19 +7324,17 @@ 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. - - The next generation of private messaging - Ново поколение поверителни съобщения - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита. No comment provided by engineer. - - The profile is only shared with your contacts. - Профилът се споделя само с вашите контакти. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6418,15 +7352,27 @@ It can happen because of some bug or when the connection is compromised.Сървърите за нови връзки на текущия ви чат профил **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + 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. + No comment provided by engineer. + Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Тези настройки са за текущия ви профил **%@**. @@ -6447,6 +7393,10 @@ It can happen because of some bug or when the connection is compromised.Това действие не може да бъде отменено - съобщенията, изпратени и получени по-рано от избраното, ще бъдат изтрити. Може да отнеме няколко минути. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Това действие не може да бъде отменено - вашият профил, контакти, съобщения и файлове ще бъдат безвъзвратно загубени. @@ -6492,10 +7442,18 @@ It can happen because of some bug or when the connection is compromised.Това е вашят еднократен линк за връзка! 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. + No comment provided by engineer. + 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. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Тази настройка се прилага за съобщения в текущия ви профил **%@**. @@ -6525,9 +7483,8 @@ It can happen because of some bug or when the connection is compromised.За да направите нова връзка No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6546,6 +7503,23 @@ You will be prompted to complete authentication before this feature is enabled.< Ще бъдете подканени да извършите идентификация, преди тази функция да бъде активирана. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. За да запишете гласово съобщение, моля, дайте разрешение за използване на микрофон. @@ -6556,11 +7530,19 @@ You will be prompted to complete authentication before this feature is enabled.< За да разкриете своя скрит профил, въведете пълна парола в полето за търсене на страницата **Вашите чат профили**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. За поддръжка на незабавни push известия, базата данни за чат трябва да бъде мигрирана. No comment provided by engineer. + + 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. За да проверите криптирането от край до край с вашия контакт, сравнете (или сканирайте) кода на вашите устройства. @@ -6575,6 +7557,10 @@ You will be prompted to complete authentication before this feature is enabled.< Избор на инкогнито при свързване. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity No comment provided by engineer. @@ -6647,6 +7633,10 @@ You will be prompted to complete authentication before this feature is enabled.< Отблокирай член? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Неочаквано състояние на миграция @@ -6694,7 +7684,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6731,13 +7721,17 @@ To connect, please ask your contact to create another connection link and check Unmute Уведомявай - swipe action + notification label action Unread Непрочетено swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. На новите членове се изпращат до последните 100 съобщения. @@ -6762,6 +7756,10 @@ To connect, please ask your contact to create another connection link and check Update settings? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Актуализирането на настройките ще свърже отново клиента към всички сървъри. @@ -6799,16 +7797,32 @@ To connect, please ask your contact to create another connection link and check Архивът се качва No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Използвай .onion хостове No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Използвай сървърите на SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Използвай чата @@ -6819,6 +7833,14 @@ To connect, please ask your contact to create another connection link and check Използвай текущия профил No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Използвай за нови връзки @@ -6857,6 +7879,14 @@ To connect, please ask your contact to create another connection link and check Използвай сървър No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Използвайте приложението по време на разговора. @@ -6866,15 +7896,18 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. - - User profile - Потребителски профил + + Use web port No comment provided by engineer. User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Използват се сървърите на SimpleX Chat. @@ -6945,11 +7978,19 @@ To connect, please ask your contact to create another connection link and check Видео и файлове до 1gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Виж кода за сигурност No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Видима история @@ -6965,8 +8006,8 @@ To connect, please ask your contact to create another connection link and check Гласовите съобщения са забранени в този чат. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Гласовите съобщения са забранени в тази група. No comment provided by engineer. @@ -7058,9 +8099,8 @@ To connect, please ask your contact to create another connection link and check При свързване на аудио и видео разговори. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7104,7 +8144,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7128,11 +8168,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - You - Вие - No comment provided by engineer. - You **must not** use the same database on two devices. **Не трябва** да използвате една и съща база данни на две устройства. @@ -7158,6 +8193,10 @@ To connect, please ask your contact to create another connection link and check Вече сте вече свързани с %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Вече се свързвате с %@. @@ -7218,6 +8257,10 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure servers via settings. + No comment provided by engineer. + You can create it later Можете да го създадете по-късно @@ -7257,6 +8300,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Можете да зададете визуализация на известията на заключен екран през настройките. @@ -7272,11 +8319,6 @@ Repeat join request? Можете да споделите този адрес с вашите контакти, за да им позволите да се свържат с **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Можете да споделите адреса си като линк или QR код - всеки може да се свърже с вас. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Можете да започнете чат през Настройки на приложението / База данни или като рестартирате приложението @@ -7299,23 +8341,23 @@ Repeat join request? You can view invitation link again in connection details. Можете да видите отново линкът за покана в подробностите за връзката. - No comment provided by engineer. + alert message You can't send messages! Не може да изпращате съобщения! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Вие контролирате през кой сървър(и) **да получавате** съобщенията, вашите контакти – сървърите, които използвате, за да им изпращате съобщения. - 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 already requested connection via this address! Вече сте заявили връзка през този адрес! @@ -7380,6 +8422,10 @@ Repeat connection request? Изпратихте покана за групата No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! Ще бъдете свързани с групата, когато устройството на домакина на групата е онлайн, моля, изчакайте или проверете по-късно! @@ -7415,6 +8461,10 @@ Repeat connection request? Все още ще получавате обаждания и известия от заглушени профили, когато са активни. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Ще спрете да получавате съобщения от тази група. Историята на чата ще бъде запазена. @@ -7435,29 +8485,14 @@ Repeat connection request? Използвате инкогнито профил за тази група - за да се предотврати споделянето на основния ви профил, поканите на контакти не са разрешени No comment provided by engineer. - - Your %@ servers - Вашите %@ сървъри - No comment provided by engineer. - Your ICE servers Вашите ICE сървъри No comment provided by engineer. - - Your SMP servers - Вашите SMP сървъри - No comment provided by engineer. - Your SimpleX address - Вашият SimpleX адрес - No comment provided by engineer. - - - Your XFTP servers - Вашите XFTP сървъри + Вашият адрес в SimpleX No comment provided by engineer. @@ -7467,19 +8502,27 @@ Repeat connection request? Your chat database - Вашата чат база данни + Вашата база данни No comment provided by engineer. Your chat database is not encrypted - set passphrase to encrypt it. - Вашата чат база данни не е криптирана - задайте парола, за да я криптирате. + Вашата база данни не е криптирана - задайте парола, за да я криптирате. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Вашите чат профили No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Вашият контакт изпрати файл, който е по-голям от поддържания в момента максимален размер (%@). @@ -7495,9 +8538,13 @@ Repeat connection request? Вашите контакти ще останат свързани. 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. - Вашата текуща чат база данни ще бъде ИЗТРИТА и ЗАМЕНЕНА с импортираната. + Вашата текуща база данни ще бъде ИЗТРИТА и ЗАМЕНЕНА с импортираната. No comment provided by engineer. @@ -7525,33 +8572,34 @@ Repeat connection request? Вашият профил **%@** ще бъде споделен. 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 only shared with your contacts. + Профилът се споделя само с вашите контакти. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. 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 random profile Вашият автоматично генериран профил No comment provided by engineer. - - Your server - Вашият сървър - No comment provided by engineer. - Your server address Вашият адрес на сървъра No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Вашите настройки @@ -7592,6 +8640,10 @@ SimpleX сървърите не могат да видят вашия профи обаждането прието call status + + accepted invitation + chat list item title + admin админ @@ -7627,6 +8679,10 @@ SimpleX сървърите не могат да видят вашия профи и %lld други събития No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts No comment provided by engineer. @@ -7664,7 +8720,8 @@ SimpleX сървърите не могат да видят вашия профи blocked by admin блокиран от админ - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7778,7 +8835,7 @@ SimpleX сървърите не могат да видят вашия профи connecting… свързване… - chat list item title + No comment provided by engineer. connection established @@ -7832,7 +8889,8 @@ SimpleX сървърите не могат да видят вашия профи default (%@) по подразбиране (%@) - pref value + delete after time +pref value default (no) @@ -7856,7 +8914,7 @@ SimpleX сървърите не могат да видят вашия профи deleted group - групата изтрита + групата е изтрита rcv group event chat item @@ -7958,11 +9016,6 @@ SimpleX сървърите не могат да видят вашия профи грешка No comment provided by engineer. - - event happened - събитие се случи - No comment provided by engineer. - expired No comment provided by engineer. @@ -8129,19 +9182,19 @@ SimpleX сървърите не могат да видят вашия профи модерирано от %@ marked deleted chat item preview text + + moderator + member role + months месеци time unit - - mute - No comment provided by engineer. - never никога - No comment provided by engineer. + delete after time new message @@ -8172,8 +9225,8 @@ SimpleX сървърите не могат да видят вашия профи off изключено enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8213,6 +9266,14 @@ SimpleX сървърите не могат да видят вашия профи peer-to-peer No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption квантово устойчиво e2e криптиране @@ -8228,6 +9289,10 @@ SimpleX сървърите не могат да видят вашия профи получено потвърждение… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call отхвърлено повикване @@ -8258,6 +9323,10 @@ SimpleX сървърите не могат да видят вашия профи ви острани rcv group event chat item + + requested to connect + chat list item title + saved запазено @@ -8352,10 +9421,6 @@ last received msg: %2$@ неизвестен статус No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected No comment provided by engineer. @@ -8517,7 +9582,7 @@ last received msg: %2$@
- +
@@ -8554,7 +9619,7 @@ last received msg: %2$@
- +
@@ -8574,9 +9639,36 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
@@ -8595,7 +9687,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/contents.json b/apps/ios/SimpleX Localizations/bg.xcloc/contents.json index 5356e25a2e..66d64e6539 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/bg.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "bg", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index b92196b78b..bf7753675e 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -193,20 +193,16 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. No comment provided by engineer. @@ -217,8 +213,8 @@ **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. No comment provided by engineer. @@ -1251,8 +1247,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1271,8 +1267,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1751,24 +1747,24 @@ Group links No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. No comment provided by engineer. Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1899,8 +1895,8 @@ Immediately No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -2020,8 +2016,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2207,8 +2203,8 @@ Message reactions are prohibited in this chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. No comment provided by engineer. @@ -2239,8 +2235,8 @@ Migration is completed No comment provided by engineer. - - Migrations: %@ + + Migrations: No comment provided by engineer. @@ -2409,8 +2405,8 @@ Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2477,8 +2473,8 @@ Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2537,8 +2533,8 @@ Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -3373,8 +3369,8 @@ Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3418,16 +3414,16 @@ 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. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -3490,8 +3486,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3724,8 +3720,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -3876,10 +3872,6 @@ To connect, please ask your contact to create another connection link and check You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index a02203e630..0400839cb0 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (lze kopírovat) @@ -109,6 +82,7 @@ %@ downloaded + %@ staženo No comment provided by engineer. @@ -126,6 +100,16 @@ %@ je ověřený No comment provided by engineer. + + %@ server + %@ server + No comment provided by engineer. + + + %@ servers + %@ servery + No comment provided by engineer. + %@ uploaded No comment provided by engineer. @@ -135,6 +119,11 @@ %@ se chce připojit! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -154,11 +143,35 @@ %d dní time interval + + %d file(s) are still being downloaded. + %d soubor(y) stále stahován(y). + forward confirmation reason + + + %d file(s) failed to download. + %d soubor(y) se nepodařilo stáhnout. + forward confirmation reason + + + %d file(s) were deleted. + %d soubor(y) smazán(y). + forward confirmation reason + + + %d file(s) were not downloaded. + %d soubor(y) nestažen(y). + forward confirmation reason + %d hours %d hodin time interval + + %d messages not forwarded + alert title + %d min %d minuty @@ -174,6 +187,10 @@ %d sek time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d přeskočené zprávy @@ -215,14 +232,17 @@ %lld messages blocked + %lld zprávy blokovaný No comment provided by engineer. %lld messages blocked by admin + %lld zprávy blokovaný adminem No comment provided by engineer. %lld messages marked deleted + %lld zprávy označeno jako smazáno No comment provided by engineer. @@ -239,11 +259,6 @@ %d nové jazyky rozhraní No comment provided by engineer. - - %lld second(s) - %lld vteřin - No comment provided by engineer. - %lld seconds %lld vteřin @@ -294,44 +309,30 @@ %u zpráv přeskočeno. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) No comment provided by engineer. (this device v%@) + (toto zařízení v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt. + + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte). No comment provided by engineer. @@ -344,11 +345,15 @@ **Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence. @@ -373,11 +378,6 @@ \*tučně* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -411,11 +411,6 @@ - historie úprav. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec time to disappear @@ -428,7 +423,8 @@ 1 day 1 den - time interval + delete after time +time interval 1 hour @@ -443,12 +439,26 @@ 1 month 1 měsíc - time interval + delete after time +time interval 1 week 1 týden - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. 5 minutes @@ -465,11 +475,6 @@ 30 vteřin No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -519,19 +524,13 @@ Přerušit změnu adresy? No comment provided by engineer. - - About SimpleX - O SimpleX - No comment provided by engineer. - About SimpleX Chat O SimpleX chat No comment provided by engineer. - - About SimpleX address - O SimpleX adrese + + About operators No comment provided by engineer. @@ -542,8 +541,12 @@ Accept Přijmout accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + No comment provided by engineer. Accept connection request? @@ -559,7 +562,11 @@ Accept incognito Přijmout inkognito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + No comment provided by engineer. Acknowledged @@ -569,6 +576,10 @@ Acknowledgement errors No comment provided by engineer. + + Active + token status text + Active connections No comment provided by engineer. @@ -578,13 +589,12 @@ 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 contact + + Add friends No comment provided by engineer. - - Add preset servers - Přidejte přednastavené servery + + Add list No comment provided by engineer. @@ -602,16 +612,36 @@ Přidejte servery skenováním QR kódů. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Přidat do jiného zařízení No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message Přidat uvítací zprávu No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -634,6 +664,14 @@ Změna adresy bude přerušena. Budou použity staré přijímací adresy. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -652,6 +690,10 @@ Advanced settings No comment provided by engineer. + + All + No comment provided by engineer. + All app data is deleted. Všechna data aplikace jsou smazána. @@ -662,13 +704,17 @@ Všechny chaty a zprávy budou smazány – tuto akci nelze vrátit zpět! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. Všechna data se při zadání vymažou. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. @@ -676,6 +722,10 @@ Všichni členové skupiny zůstanou připojeni. 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! No comment provided by engineer. @@ -691,6 +741,14 @@ All profiles + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -764,6 +822,10 @@ Povolit nevratné smazání odeslaných zpráv. (24 hodin) No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. No comment provided by engineer. @@ -840,11 +902,20 @@ Vytvořit prázdný chat profil se zadaným názvem a otevřít aplikaci jako obvykle. No comment provided by engineer. + + Another reason + report reason + 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: %@ @@ -859,6 +930,10 @@ Aplikace šifruje nové místní soubory (s výjimkou videí). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon Ikona aplikace @@ -874,6 +949,10 @@ Přístupový kód aplikace je nahrazen sebedestrukčním přístupovým heslem. No comment provided by engineer. + + App session + No comment provided by engineer. + App version Verze aplikace @@ -897,6 +976,18 @@ Apply to No comment provided by engineer. + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? + No comment provided by engineer. + Archive and upload No comment provided by engineer. @@ -905,6 +996,18 @@ Archive contacts to chat later. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts No comment provided by engineer. @@ -973,6 +1076,10 @@ Automaticky přijímat obrázky No comment provided by engineer. + + Auto-accept settings + alert title + Back Zpět @@ -996,10 +1103,22 @@ Špatný hash zprávy No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + No comment provided by engineer. + Better messages Lepší zprávy @@ -1009,6 +1128,22 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1079,11 +1214,29 @@ 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. + + Business address + No comment provided by engineer. + + + Business chats + No comment provided by engineer. + + + Businesses + 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). Podle chat profilu (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! @@ -1127,7 +1280,8 @@ Cancel Zrušit - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1145,7 +1299,7 @@ Cannot receive file Nelze přijmout soubor - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1160,6 +1314,14 @@ Změnit No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + authentication reason + Change database passphrase? Změnit přístupovou frázi databáze? @@ -1204,11 +1366,18 @@ Change self-destruct passcode Změnit sebedestrukční heslo authentication reason - set passcode view +set passcode view - - Chat archive - Chat se archivuje + + Chat + No comment provided by engineer. + + + Chat already exists + No comment provided by engineer. + + + Chat already exists! No comment provided by engineer. @@ -1266,19 +1435,44 @@ Předvolby chatu No comment provided by engineer. + + Chat preferences were changed. + alert message + + + Chat profile + Profil uživatele + No comment provided by engineer. + Chat theme No comment provided by engineer. + + 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. + Chats Chaty No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Zkontrolujte adresu serveru a zkuste to znovu. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1326,6 +1520,14 @@ Vyčistit konverzaci? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? No comment provided by engineer. @@ -1343,6 +1545,10 @@ Color mode No comment provided by engineer. + + Community guidelines violation + report reason + Compare file Porovnat soubor @@ -1357,13 +1563,41 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Konfigurace serverů ICE No comment provided by engineer. - - Configured %@ servers + + Configure server operators No comment provided by engineer. @@ -1411,6 +1645,10 @@ Confirm upload No comment provided by engineer. + + Confirmed + token status text + Connect Připojit @@ -1512,6 +1750,10 @@ This is your own one-time link! Connection and servers status. No comment provided by engineer. + + Connection blocked + No comment provided by engineer. + Connection error Chyba připojení @@ -1522,6 +1764,15 @@ This is your own one-time link! Chyba spojení (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications No comment provided by engineer. @@ -1531,6 +1782,14 @@ This is your own one-time link! Požadavek na připojení byl odeslán! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1600,6 +1859,10 @@ This is your own one-time link! Kontakty mohou označit zprávy ke smazání; vy je budete moci zobrazit. No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue Pokračovat @@ -1623,6 +1886,10 @@ This is your own one-time link! Verze jádra: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -1632,6 +1899,10 @@ This is your own one-time link! Vytvořit No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Vytvořit SimpleX adresu @@ -1641,11 +1912,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - Vytvořit adresu, aby se s vámi lidé mohli spojit. - No comment provided by engineer. - Create file Vytvořit soubor @@ -1665,6 +1931,10 @@ This is your own one-time link! Vytvořit odkaz No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻 @@ -1672,6 +1942,7 @@ This is your own one-time link! Create profile + Vytvořte si profil No comment provided by engineer. @@ -1701,11 +1972,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - Vytvořeno na %@ - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -1719,6 +1985,10 @@ This is your own one-time link! Aktuální heslo No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Aktuální přístupová fráze… @@ -1738,6 +2008,10 @@ This is your own one-time link! Vlastní čas No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -1866,8 +2140,8 @@ This is your own one-time link! Delete Smazat - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -1901,14 +2175,12 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - Smazat archiv + + Delete chat No comment provided by engineer. - - Delete chat archive? - Smazat archiv chatu? + + Delete chat messages from your device. No comment provided by engineer. @@ -1921,6 +2193,10 @@ This is your own one-time link! Smazat chat profil? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Smazat připojení @@ -1994,6 +2270,10 @@ This is your own one-time link! Smazat odkaz? No comment provided by engineer. + + Delete list? + alert title + Delete member message? Smazat zprávu člena? @@ -2007,7 +2287,7 @@ This is your own one-time link! Delete messages Smazat zprávy - No comment provided by engineer. + alert button Delete messages after @@ -2024,6 +2304,10 @@ This is your own one-time link! Smazat starou databázi? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Smazat čekající připojení? @@ -2039,6 +2323,10 @@ This is your own one-time link! Odstranit frontu server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. No comment provided by engineer. @@ -2070,6 +2358,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Doručenka @@ -2161,8 +2453,12 @@ This is your own one-time link! Přímé zprávy chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + + + Direct messages between members are prohibited. Přímé zprávy mezi členy jsou v této skupině zakázány. No comment provided by engineer. @@ -2176,6 +2472,14 @@ This is your own one-time link! Vypnutí zámku SimpleX authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all Vypnout pro všechny @@ -2200,8 +2504,8 @@ This is your own one-time link! Mizící zprávy jsou v tomto chatu zakázány. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Mizící zprávy jsou v této skupině zakázány. No comment provided by engineer. @@ -2255,6 +2559,14 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address Nevytvářet adresu @@ -2265,11 +2577,19 @@ This is your own one-time link! Nepovolovat No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again Znovu neukazuj No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat Snížit a otevřít chat @@ -2277,7 +2597,8 @@ This is your own one-time link! Download - chat item action + alert button +chat item action Download errors @@ -2292,6 +2613,10 @@ This is your own one-time link! Stáhnout soubor server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2318,6 +2643,10 @@ This is your own one-time link! Trvání No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Upravit @@ -2338,6 +2667,10 @@ This is your own one-time link! Povolit (zachovat přepsání) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Zapnutí zámku SimpleX @@ -2351,7 +2684,7 @@ This is your own one-time link! Enable automatic message deletion? Povolit automatické mazání zpráv? - No comment provided by engineer. + alert title Enable camera access @@ -2471,6 +2804,10 @@ This is your own one-time link! Encryption re-negotiation failed. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Zadat heslo @@ -2532,26 +2869,33 @@ This is your own one-time link! Chyba přerušení změny adresy No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Chyba při přijímání žádosti o kontakt No comment provided by engineer. - - Error accessing database file - Chyba přístupu k souboru databáze - No comment provided by engineer. - Error adding member(s) Chyba přidávání člena(ů) No comment provided by engineer. + + Error adding server + alert title + Error changing address Chuba změny adresy No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Chyba při změně role @@ -2562,6 +2906,14 @@ This is your own one-time link! Chyba změny nastavení No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2581,6 +2933,10 @@ This is your own one-time link! Chyba při vytváření odkazu skupiny No comment provided by engineer. + + Error creating list + alert title + Error creating member contact Chyba vytvoření kontaktu člena @@ -2595,6 +2951,10 @@ This is your own one-time link! Chyba při vytváření profilu! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file Chyba dešifrování souboru @@ -2673,9 +3033,12 @@ This is your own one-time link! Chyba při připojování ke skupině No comment provided by engineer. - - Error loading %@ servers - Chyba načítání %@ serverů + + Error loading servers + alert title + + + Error migrating settings No comment provided by engineer. @@ -2685,7 +3048,7 @@ This is your own one-time link! Error receiving file Chyba při příjmu souboru - No comment provided by engineer. + alert title Error reconnecting server @@ -2695,25 +3058,32 @@ This is your own one-time link! Error reconnecting servers No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member Chyba při odebrání člena No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Chyba při ukládání serverů %@ - No comment provided by engineer. - Error saving ICE servers Chyba při ukládání serverů ICE No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile Chyba při ukládání profilu skupiny @@ -2729,6 +3099,10 @@ This is your own one-time link! Při ukládání přístupové fráze do klíčenky došlo k chybě No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2772,16 +3146,24 @@ This is your own one-time link! Chyba při zastavení chatu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Chyba při přepínání profilu! - No comment provided by engineer. + alertTitle Error synchronizing connection Chyba synchronizace připojení No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link Chyba aktualizace odkazu skupiny @@ -2792,6 +3174,10 @@ This is your own one-time link! Chyba aktualizace zprávy No comment provided by engineer. + + Error updating server + alert title + Error updating settings Chyba při aktualizaci nastavení @@ -2818,8 +3204,9 @@ This is your own one-time link! Error: %@ Chyba: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2835,6 +3222,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. I při vypnutí v konverzaci. @@ -2849,6 +3240,10 @@ This is your own one-time link! Expand chat item action + + Expired + token status text + Export database Export databáze @@ -2887,18 +3282,40 @@ This is your own one-time link! Rychle a bez čekání, než bude odesílatel online! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite Oblíbené swipe action + + Favorites + No comment provided by engineer. + File error - No comment provided by engineer. + file error alert title + + + File errors: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -2950,8 +3367,8 @@ This is your own one-time link! Soubory a média chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Soubory a média jsou zakázány v této skupině. No comment provided by engineer. @@ -3017,19 +3434,59 @@ This is your own one-time link! Opravit nepodporované členem skupiny No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + servers error + For console Pro konzoli No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3038,6 +3495,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3079,11 +3540,6 @@ Error: %2$@ Celé jméno (volitelně) No comment provided by engineer. - - Full name: - Celé jméno: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -3103,6 +3559,10 @@ Error: %2$@ GIFy a nálepky No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! message preview @@ -3164,40 +3624,6 @@ Error: %2$@ Odkazy na skupiny No comment provided by engineer. - - Group members can add message reactions. - Členové skupin mohou přidávat reakce na zprávy. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) - No comment provided by engineer. - - - Group members can send SimpleX links. - No comment provided by engineer. - - - Group members can send direct messages. - Členové skupiny mohou posílat přímé zprávy. - No comment provided by engineer. - - - Group members can send disappearing messages. - Členové skupiny mohou posílat mizící zprávy. - No comment provided by engineer. - - - Group members can send files and media. - Členové skupiny mohou posílat soubory a média. - No comment provided by engineer. - - - Group members can send voice messages. - Členové skupiny mohou posílat hlasové zprávy. - No comment provided by engineer. - Group message: Skupinová zpráva: @@ -3238,11 +3664,19 @@ Error: %2$@ Skupina bude smazána pro vás - toto nelze vzít zpět! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Pomoc No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Skryté @@ -3292,10 +3726,17 @@ Error: %2$@ Jak SimpleX funguje No comment provided by engineer. + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + No comment provided by engineer. + How it works - Jak to funguje - No comment provided by engineer. + alert button How to @@ -3321,6 +3762,10 @@ Error: %2$@ Servery ICE (jeden na řádek) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Pokud se nemůžete setkat osobně, zobrazte QR kód ve videohovoru nebo sdílejte odkaz. @@ -3361,8 +3806,8 @@ Error: %2$@ Ihned No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Odolná vůči spamu a zneužití No comment provided by engineer. @@ -3393,6 +3838,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -3420,6 +3870,14 @@ Error: %2$@ In-call sounds No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Inkognito @@ -3488,6 +3946,11 @@ Error: %2$@ Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Okamžitě + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3495,11 +3958,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Okamžitě - No comment provided by engineer. - Interface Rozhranní @@ -3509,6 +3967,26 @@ Error: %2$@ Interface colors No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code No comment provided by engineer. @@ -3541,7 +4019,7 @@ Error: %2$@ Invalid server address! Neplatná adresa serveru! - No comment provided by engineer. + alert title Invalid status @@ -3563,6 +4041,10 @@ Error: %2$@ Pozvat členy No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Pozvat do skupiny @@ -3578,8 +4060,8 @@ Error: %2$@ Nevratné mazání zpráv je v tomto chatu zakázáno. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Nevratné mazání zpráv je v této skupině zakázáno. No comment provided by engineer. @@ -3662,7 +4144,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3674,7 +4156,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3711,6 +4193,14 @@ This is your link for group %@! Opustit swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Opustit skupinu @@ -3748,6 +4238,18 @@ This is your link for group %@! Linked desktops No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Živé zprávy! @@ -3758,11 +4260,6 @@ This is your link for group %@! Živé zprávy No comment provided by engineer. - - Local - Místní - No comment provided by engineer. - Local name Místní název @@ -3783,11 +4280,6 @@ This is your link for group %@! Režim zámku No comment provided by engineer. - - Make a private connection - Vytvořte si soukromé připojení - No comment provided by engineer. - Make one message disappear Nechat jednu zprávu zmizet @@ -3798,21 +4290,11 @@ This is your link for group %@! Změnit profil na soukromý! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Ujistěte se, že adresy %@ serverů jsou ve správném formátu, oddělené řádky a nejsou duplicitní (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Mnoho lidí se ptalo: *Pokud SimpleX nemá žádné uživatelské identifikátory, jak může doručovat zprávy?* - No comment provided by engineer. - Mark deleted for everyone Označit jako smazané pro všechny @@ -3855,6 +4337,14 @@ This is your link for group %@! Member inactive item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Role člena se změní na "%@". Všichni členové skupiny budou upozorněni. @@ -3865,11 +4355,57 @@ This is your link for group %@! Role člena se změní na "%@". Člen obdrží novou pozvánku. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Člen bude odstraněn ze skupiny - toto nelze vzít zpět! No comment provided by engineer. + + Members can add message reactions. + Členové skupin mohou přidávat reakce na zprávy. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + Členové skupiny mohou posílat přímé zprávy. + No comment provided by engineer. + + + Members can send disappearing messages. + Členové skupiny mohou posílat mizící zprávy. + No comment provided by engineer. + + + Members can send files and media. + Členové skupiny mohou posílat soubory a média. + No comment provided by engineer. + + + Members can send voice messages. + Členové skupiny mohou posílat hlasové zprávy. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus No comment provided by engineer. @@ -3915,8 +4451,8 @@ This is your link for group %@! Reakce na zprávy jsou v tomto chatu zakázány. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Reakce na zprávy jsou v této skupině zakázány. No comment provided by engineer. @@ -3928,6 +4464,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3963,6 +4503,10 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received No comment provided by engineer. @@ -3971,6 +4515,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4027,9 +4575,9 @@ This is your link for group %@! Přenesení dokončeno No comment provided by engineer. - - Migrations: %@ - Migrace: %@ + + Migrations: + Migrace: No comment provided by engineer. @@ -4047,6 +4595,10 @@ This is your link for group %@! Upraveno v: %@ copied message info + + More + swipe action + More improvements are coming soon! Další vylepšení se chystají již brzy! @@ -4056,6 +4608,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Pravděpodobně je toto spojení smazáno. @@ -4069,7 +4625,11 @@ This is your link for group %@! Mute Ztlumit - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4090,6 +4650,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4098,6 +4662,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Nastavení sítě @@ -4108,11 +4676,23 @@ This is your link for group %@! Stav sítě No comment provided by engineer. + + New + token status text + New Passcode Nové heslo No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + No comment provided by engineer. + New chat No comment provided by engineer. @@ -4131,11 +4711,6 @@ This is your link for group %@! Nový kontakt: notification - - New database archive - Archiv nové databáze - No comment provided by engineer. - New desktop app! Nová desktopová aplikace! @@ -4146,6 +4721,10 @@ This is your link for group %@! Nově zobrazované jméno No comment provided by engineer. + + New events + notification + New in %@ Nový V %@ @@ -4170,6 +4749,10 @@ This is your link for group %@! Nová přístupová fráze… No comment provided by engineer. + + New server + No comment provided by engineer. + No Ne @@ -4180,6 +4763,18 @@ This is your link for group %@! Žádné heslo aplikace Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Nebyl vybrán žádný kontakt @@ -4223,28 +4818,90 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection No comment provided by engineer. + + No permission to record speech + No comment provided by engineer. + + + No permission to record video + No comment provided by engineer. + No permission to record voice message Nemáte oprávnění nahrávat hlasové zprávy No comment provided by engineer. + + No push server + Místní + No comment provided by engineer. + No received or sent files Žádné přijaté ani odeslané soubory No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + Bez uživatelských identifikátorů + No comment provided by engineer. + Not compatible! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Oznámení @@ -4255,6 +4912,18 @@ This is your link for group %@! Oznámení jsou zakázána! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4276,18 +4945,13 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database Stará databáze No comment provided by engineer. - - Old database archive - Archiv staré databáze - No comment provided by engineer. - One-time invitation link Jednorázový zvací odkaz @@ -4312,8 +4976,12 @@ Vyžaduje povolení sítě VPN. Onion hostitelé nebudou použiti. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**. No comment provided by engineer. @@ -4336,6 +5004,14 @@ Vyžaduje povolení sítě VPN. Pouze majitelé skupin mohou povolit zasílání hlasových zpráv. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Reakce na zprávy můžete přidávat pouze vy. @@ -4389,13 +5065,17 @@ Vyžaduje povolení sítě VPN. Open Otevřít - No comment provided by engineer. + alert action Open Settings Otevřít nastavení No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Otevřete chat @@ -4406,32 +5086,38 @@ Vyžaduje povolení sítě VPN. Otevřete konzolu chatu authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. + + Open link? + alert title + Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Otevřít uživatelské profily - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli. - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -4448,13 +5134,22 @@ Vyžaduje povolení sítě VPN. Or show this code No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + alert message PING count @@ -4491,6 +5186,10 @@ Vyžaduje povolení sítě VPN. Heslo nastaveno! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Heslo k zobrazení @@ -4521,13 +5220,8 @@ Vyžaduje povolení sítě VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - Lidé se s vámi mohou spojit pouze prostřednictvím odkazů, které sdílíte. - No comment provided by engineer. - - - Periodically + + Periodic Pravidelně No comment provided by engineer. @@ -4622,11 +5316,27 @@ Error: %@ Heslo uložte bezpečně, v případě jeho ztráty jej NEBUDE možné změnit. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Polské rozhraní No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Je možné, že otisk certifikátu v adrese serveru je nesprávný @@ -4637,16 +5347,15 @@ Error: %@ Zachování posledního návrhu zprávy s přílohami. No comment provided by engineer. - - Preset server - Přednastavený server - No comment provided by engineer. - Preset server address Přednastavená adresa serveru No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Náhled @@ -4661,16 +5370,32 @@ Error: %@ Ochrana osobních údajů a zabezpečení No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Nové vymezení soukromí No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Soukromé názvy souborů No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing No comment provided by engineer. @@ -4705,14 +5430,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password Heslo profilu @@ -4725,7 +5442,7 @@ Error: %@ Profile update will be sent to your contacts. Aktualizace profilu bude zaslána vašim kontaktům. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4747,6 +5464,10 @@ Error: %@ Zakázat reakce na zprávy. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. No comment provided by engineer. @@ -4808,6 +5529,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Nabízená oznámení @@ -4845,25 +5570,20 @@ 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/app-settings.html#your-simplex-contact-address). - Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/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. - Další informace najdete v našem repozitáři GitHub. - 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). @@ -4983,11 +5703,23 @@ Enable in *Network & servers* settings. Snížení spotřeby baterie No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Odmítnout reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5014,6 +5746,10 @@ Enable in *Network & servers* settings. Odstranit No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5073,6 +5809,46 @@ Enable in *Network & servers* settings. Odpověď chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Povinné @@ -5152,6 +5928,10 @@ Enable in *Network & servers* settings. Odhalit chat item action + + Review conditions + No comment provided by engineer. + Revoke Odvolat @@ -5181,6 +5961,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5192,17 +5976,18 @@ Enable in *Network & servers* settings. Save Uložit - chat item action + alert button +chat item action Save (and notify contacts) Uložit (a informovat kontakty) - No comment provided by engineer. + alert button Save and notify contact Uložit a upozornit kontakt - No comment provided by engineer. + alert button Save and notify group members @@ -5218,21 +6003,15 @@ Enable in *Network & servers* settings. Uložit a aktualizovat profil skupiny No comment provided by engineer. - - Save archive - Uložit archiv - No comment provided by engineer. - - - Save auto-accept settings - Uložit nastavení automatického přijímání - No comment provided by engineer. - Save group profile Uložení profilu skupiny No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Uložte heslo a otevřete chat @@ -5246,7 +6025,7 @@ Enable in *Network & servers* settings. Save preferences? Uložit předvolby? - No comment provided by engineer. + alert title Save profile password @@ -5261,18 +6040,17 @@ Enable in *Network & servers* settings. Save servers? Uložit servery? - No comment provided by engineer. - - - Save settings? - Uložit nastavení? - No comment provided by engineer. + alert title Save welcome message? Uložit uvítací zprávu? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5290,6 +6068,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5363,6 +6145,10 @@ Enable in *Network & servers* settings. Vybrat chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5447,9 +6233,8 @@ Enable in *Network & servers* settings. Odeslat oznámení No comment provided by engineer. - - Send notifications: - Odeslat oznámení: + + Send private reports No comment provided by engineer. @@ -5474,7 +6259,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Odesílatel zrušil přenos souboru. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5566,6 +6351,14 @@ Enable in *Network & servers* settings. Sent via proxy No comment provided by engineer. + + Server + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5578,6 +6371,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo @@ -5627,6 +6432,10 @@ Enable in *Network & servers* settings. Nastavit 1 den No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Nastavení jména kontaktu… @@ -5646,6 +6455,10 @@ Enable in *Network & servers* settings. Nastavte jej namísto ověřování systému. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Nastavit heslo @@ -5675,6 +6488,10 @@ Enable in *Network & servers* settings. Nastavení No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5682,22 +6499,35 @@ Enable in *Network & servers* settings. Share Sdílet - chat item action + alert action +chat item action Share 1-time link Sdílet jednorázovou pozvánku No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Sdílet adresu No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Sdílet adresu s kontakty? - No comment provided by engineer. + alert title Share from other apps. @@ -5708,6 +6538,10 @@ Enable in *Network & servers* settings. Sdílet odkaz No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -5721,6 +6555,10 @@ Enable in *Network & servers* settings. Sdílet s kontakty No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -5771,6 +6609,10 @@ Enable in *Network & servers* settings. SimpleX Adresa No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Zabezpečení SimpleX chatu bylo auditováno společností Trail of Bits. @@ -5801,6 +6643,18 @@ Enable in *Network & servers* settings. Adresa SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX kontaktní adresa @@ -5821,8 +6675,8 @@ Enable in *Network & servers* settings. Odkazy na SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -5834,6 +6688,10 @@ Enable in *Network & servers* settings. Jednorázová pozvánka SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Zjednodušený inkognito režim @@ -5862,6 +6720,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -5875,11 +6737,21 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Někdo notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. No comment provided by engineer. @@ -5920,11 +6792,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Zastavte chat pro povolení akcí databáze - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Zastavení chatu pro export, import nebo smazání databáze chatu. Během zastavení chatu nebudete moci přijímat a odesílat zprávy. @@ -5953,17 +6820,21 @@ Enable in *Network & servers* settings. Stop sharing Přestat sdílet - No comment provided by engineer. + alert action Stop sharing address? Přestat sdílet adresu? - No comment provided by engineer. + alert title Stopping chat No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong blur media @@ -5990,6 +6861,14 @@ Enable in *Network & servers* settings. Podpořte SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Systém @@ -6009,6 +6888,10 @@ Enable in *Network & servers* settings. Časový limit připojení TCP No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6024,11 +6907,19 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Vyfotit No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Klepněte na tlačítko @@ -6063,13 +6954,17 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + file error alert title Test failed at step %@. Test selhal v kroku %@. server test failure + + Test notifications + No comment provided by engineer. + Test server Testovací server @@ -6083,7 +6978,7 @@ Enable in *Network & servers* settings. Tests failed! Testy selhaly! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6100,11 +6995,6 @@ Enable in *Network & servers* settings. Díky uživatelům - přispívejte prostřednictvím Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - 1. Platforma bez identifikátorů uživatelů - soukromá už od záměru. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6117,6 +7007,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6130,6 +7024,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Připojení, které jste přijali, bude zrušeno! @@ -6150,6 +7048,11 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. Hash předchozí zprávy se liší. @@ -6173,19 +7076,17 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - Nová generace soukromých zpráv - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Stará databáze nebyla během přenášení odstraněna, lze ji smazat. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil je sdílen pouze s vašimi kontakty. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6203,14 +7104,26 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Servery pro nová připojení vašeho aktuálního chat profilu **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Toto nastavení je pro váš aktuální profil **%@**. @@ -6231,6 +7144,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Tuto akci nelze vzít zpět - zprávy odeslané a přijaté dříve, než bylo zvoleno, budou smazány. Může to trvat několik minut. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Tuto akci nelze vzít zpět - váš profil, kontakty, zprávy a soubory budou nenávratně ztraceny. @@ -6270,10 +7187,18 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován This is your own one-time link! 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. + No comment provided by engineer. + 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. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Toto nastavení platí pro zprávy ve vašem aktuálním chat profilu **%@**. @@ -6302,9 +7227,8 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Vytvoření nového připojení No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6323,6 +7247,23 @@ You will be prompted to complete authentication before this feature is enabled.< Před zapnutím této funkce budete vyzváni k dokončení ověření. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Chcete-li nahrávat hlasové zprávy, udělte povolení k použití mikrofonu. @@ -6333,11 +7274,19 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Chcete-li odhalit svůj skrytý profil, zadejte celé heslo do vyhledávacího pole na stránce **Chat profily**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Pro podporu doručování okamžitých upozornění musí být přenesena chat databáze. No comment provided by engineer. + + 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. Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních. @@ -6352,6 +7301,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Změnit inkognito režim při připojení. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity No comment provided by engineer. @@ -6418,6 +7371,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Neočekávaný stav přenášení @@ -6465,7 +7422,7 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6500,13 +7457,17 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Unmute Zrušit ztlumení - swipe action + notification label action Unread Nepřečtený swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -6530,6 +7491,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Update settings? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Aktualizací nastavení se klient znovu připojí ke všem serverům. @@ -6565,16 +7530,32 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Použít hostitele .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Používat servery SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Použijte chat @@ -6585,6 +7566,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít aktuální profil No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Použít pro nová připojení @@ -6621,6 +7610,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít server No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6629,15 +7626,18 @@ 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. - - User profile - Profil uživatele + + Use web port No comment provided by engineer. User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Používat servery SimpleX Chat. @@ -6702,11 +7702,19 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Videa a soubory až do velikosti 1 gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Zobrazení bezpečnostního kódu No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -6721,8 +7729,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Hlasové zprávy jsou v tomto chatu zakázány. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Hlasové zprávy jsou v této skupině zakázány. No comment provided by engineer. @@ -6809,9 +7817,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Když někdo požádá o připojení, můžete žádost přijmout nebo odmítnout. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -6850,7 +7857,7 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -6874,11 +7881,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu XFTP server No comment provided by engineer. - - You - Vy - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. @@ -6903,6 +7905,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Již jste připojeni k %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. @@ -6955,6 +7961,10 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure servers via settings. + No comment provided by engineer. + You can create it later Můžete vytvořit později @@ -6992,6 +8002,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Náhled oznámení na zamykací obrazovce můžete změnit v nastavení. @@ -7007,11 +8021,6 @@ Repeat join request? Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Chat můžete zahájit prostřednictvím aplikace Nastavení / Databáze nebo restartováním aplikace @@ -7033,23 +8042,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! Nemůžete posílat zprávy! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Sami řídíte, přes který server(y) **přijímat** zprávy, své kontakty – servery, které používáte k odesílání zpráv. - 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 already requested connection via this address! No comment provided by engineer. @@ -7111,6 +8120,10 @@ Repeat connection request? Odeslali jste pozvánku do skupiny No comment provided by engineer. + + You should receive notifications. + token info + 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! @@ -7144,6 +8157,10 @@ Repeat connection request? Stále budete přijímat volání a upozornění od umlčených profilů pokud budou aktivní. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Přestanete dostávat zprávy z této skupiny. Historie chatu bude zachována. @@ -7164,31 +8181,16 @@ Repeat connection request? Pro tuto skupinu používáte inkognito profil - abyste zabránili sdílení svého hlavního profilu, není pozvání kontaktů povoleno No comment provided by engineer. - - Your %@ servers - Vaše servery %@ - No comment provided by engineer. - Your ICE servers Vaše servery ICE No comment provided by engineer. - - Your SMP servers - Vaše servery SMP - No comment provided by engineer. - Your SimpleX address Vaše SimpleX adresa No comment provided by engineer. - - Your XFTP servers - Vaše XFTP servery - No comment provided by engineer. - Your calls Vaše hovory @@ -7204,11 +8206,19 @@ Repeat connection request? Vaše chat databáze není šifrována – nastavte přístupovou frázi pro její šifrování. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Vaše chat profily No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kontakt odeslal soubor, který je větší než aktuálně podporovaná maximální velikost (%@). @@ -7224,6 +8234,10 @@ Repeat connection request? Vaše kontakty zůstanou připojeny. 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. Vaše aktuální chat databáze bude ODSTRANĚNA a NAHRAZENA importovanou. @@ -7253,33 +8267,34 @@ Repeat connection request? Váš profil **%@** bude sdílen. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. -Servery SimpleX nevidí váš profil. + + Your profile is stored on your device and only shared with your contacts. + Profil je sdílen pouze s vašimi kontakty. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil. 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 random profile Váš náhodný profil No comment provided by engineer. - - Your server - Váš server - No comment provided by engineer. - Your server address Adresa vašeho serveru No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Vaše nastavení @@ -7320,6 +8335,10 @@ Servery SimpleX nevidí váš profil. přijatý hovor call status + + accepted invitation + chat list item title + admin správce @@ -7352,6 +8371,10 @@ Servery SimpleX nevidí váš profil. and %lld other events No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts No comment provided by engineer. @@ -7385,7 +8408,8 @@ Servery SimpleX nevidí váš profil. blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7499,7 +8523,7 @@ Servery SimpleX nevidí váš profil. connecting… připojení… - chat list item title + No comment provided by engineer. connection established @@ -7552,7 +8576,8 @@ Servery SimpleX nevidí váš profil. default (%@) výchozí (%@) - pref value + delete after time +pref value default (no) @@ -7677,10 +8702,6 @@ Servery SimpleX nevidí váš profil. chyba No comment provided by engineer. - - event happened - No comment provided by engineer. - expired No comment provided by engineer. @@ -7845,19 +8866,19 @@ Servery SimpleX nevidí váš profil. moderovaný %@ marked deleted chat item preview text + + moderator + member role + months měsíců time unit - - mute - No comment provided by engineer. - never nikdy - No comment provided by engineer. + delete after time new message @@ -7888,8 +8909,8 @@ Servery SimpleX nevidí váš profil. off vypnuto enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -7928,6 +8949,14 @@ Servery SimpleX nevidí váš profil. peer-to-peer No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -7942,6 +8971,10 @@ Servery SimpleX nevidí váš profil. obdržel potvrzení… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call odmítnutý hovor @@ -7970,6 +9003,10 @@ Servery SimpleX nevidí váš profil. odstranil vás rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. @@ -8057,10 +9094,6 @@ last received msg: %2$@ unknown status No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected No comment provided by engineer. @@ -8217,7 +9250,7 @@ last received msg: %2$@
- +
@@ -8253,7 +9286,7 @@ last received msg: %2$@
- +
@@ -8273,9 +9306,36 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
@@ -8294,7 +9354,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/contents.json b/apps/ios/SimpleX Localizations/cs.xcloc/contents.json index aaa2ed1ee0..9cd5922c24 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/cs.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "cs", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 29adfbabf0..06fd7c5a1d 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (kann kopiert werden) @@ -127,6 +100,16 @@ %@ wurde erfolgreich überprüft No comment provided by engineer. + + %@ server + %@ Server + No comment provided by engineer. + + + %@ servers + %@ Server + No comment provided by engineer. + %@ uploaded %@ hochgeladen @@ -137,6 +120,11 @@ %@ will sich mit Ihnen verbinden! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ und %lld Mitglieder @@ -157,11 +145,36 @@ %d Tage time interval + + %d file(s) are still being downloaded. + %d Datei(en) wird/werden immer noch heruntergeladen. + forward confirmation reason + + + %d file(s) failed to download. + Bei %d Datei(en) ist das Herunterladen fehlgeschlagen. + forward confirmation reason + + + %d file(s) were deleted. + %d Datei(en) wurde(n) gelöscht. + forward confirmation reason + + + %d file(s) were not downloaded. + %d Datei(en) wurde(n) nicht heruntergeladen. + forward confirmation reason + %d hours %d Stunden time interval + + %d messages not forwarded + %d Nachrichten wurden nicht weitergeleitet + alert title + %d min %d min @@ -177,6 +190,11 @@ %d s time interval + + %d seconds(s) + %d Sekunde(n) + delete after time + %d skipped message(s) %d übersprungene Nachricht(en) @@ -247,11 +265,6 @@ %lld neue Sprachen für die Bedienoberfläche No comment provided by engineer. - - %lld second(s) - %lld Sekunde(n) - No comment provided by engineer. - %lld seconds %lld Sekunden @@ -302,11 +315,6 @@ %u übersprungene Nachrichten. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (Neu) @@ -317,19 +325,9 @@ (Dieses Gerät hat v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen oder eine Verbindung über einen Link herzustellen, den Sie erhalten haben. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Neuen Kontakt hinzufügen**: Um einen Einmal-QR-Code oder -Link für Ihren Kontakt zu erzeugen. + + **Create 1-time link**: to create and share a new invitation link. + **Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen. No comment provided by engineer. @@ -337,13 +335,13 @@ **Gruppe erstellen**: Um eine neue Gruppe zu erstellen. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen). No comment provided by engineer. @@ -357,11 +355,16 @@ **Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde. No comment provided by engineer. + + **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. + **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. @@ -387,11 +390,6 @@ \*fett* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - Nachrichtenverlauf bearbeiten No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sek @@ -445,8 +438,9 @@ 1 day - täglich - time interval + Älter als ein Tag + delete after time +time interval 1 hour @@ -460,13 +454,30 @@ 1 month - monatlich - time interval + Älter als ein Monat + delete after time +time interval 1 week - wöchentlich - time interval + Älter als eine Woche + delete after time +time interval + + + 1 year + Älter als ein Jahr + delete after time + + + 1-time link + Einmal-Link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Ein Einmal-Link kann *nur mit einem Kontakt* genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 Sekunden No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,19 +543,14 @@ Wechsel der Empfängeradresse beenden? No comment provided by engineer. - - About SimpleX - Über SimpleX - No comment provided by engineer. - About SimpleX Chat Über SimpleX Chat No comment provided by engineer. - - About SimpleX address - Über die SimpleX-Adresse + + About operators + Über die Betreiber No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Annehmen accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Nutzungsbedingungen akzeptieren + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Inkognito akzeptieren accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Akzeptierte Nutzungsbedingungen + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Fehler bei der Bestätigung No comment provided by engineer. + + Active + Aktiv + token status text + Active connections Aktive Verbindungen @@ -600,14 +616,14 @@ Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. No comment provided by engineer. - - Add contact - Kontakt hinzufügen + + Add friends + Freunde aufnehmen No comment provided by engineer. - - Add preset servers - Füge voreingestellte Server hinzu + + Add list + Liste hinzufügen No comment provided by engineer. @@ -617,12 +633,17 @@ Add server - Füge Server hinzu + Server hinzufügen No comment provided by engineer. Add servers by scanning QR codes. - Fügen Sie Server durch Scannen der QR Codes hinzu. + Server durch Scannen von QR Codes hinzufügen. + No comment provided by engineer. + + + Add team members + Team-Mitglieder aufnehmen No comment provided by engineer. @@ -630,11 +651,31 @@ Einem anderen Gerät hinzufügen No comment provided by engineer. + + Add to list + Zur Liste hinzufügen + No comment provided by engineer. + Add welcome message Begrüßungsmeldung hinzufügen No comment provided by engineer. + + Add your team members to the conversations. + Nehmen Sie Team-Mitglieder in Ihre Unterhaltungen auf. + No comment provided by engineer. + + + Added media & file servers + Medien- und Dateiserver hinzugefügt + No comment provided by engineer. + + + Added message servers + Nachrichtenserver hinzugefügt + No comment provided by engineer. + Additional accent Erste Akzentfarbe @@ -660,6 +701,16 @@ Der Wechsel der Empfängeradresse wird beendet. Die bisherige Adresse wird weiter verwendet. No comment provided by engineer. + + Address or 1-time link? + Adress- oder Einmal-Link? + No comment provided by engineer. + + + Address settings + Adress-Einstellungen + No comment provided by engineer. + Admins can block a member for all. Administratoren können ein Gruppenmitglied für Alle blockieren. @@ -680,6 +731,11 @@ Erweiterte Einstellungen No comment provided by engineer. + + All + Alle + No comment provided by engineer. + All app data is deleted. Werden die App-Daten komplett gelöscht. @@ -690,13 +746,18 @@ Es werden alle Chats und Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Alle Chats werden von der Liste %@ entfernt und danach wird die Liste gelöscht. + alert message + All data is erased when it is entered. Alle Daten werden gelöscht, sobald dieser eingegeben wird. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Alle Daten werden nur auf Ihrem Gerät gespeichert. No comment provided by engineer. @@ -705,6 +766,11 @@ Alle Gruppenmitglieder bleiben verbunden. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! @@ -723,6 +789,16 @@ All profiles Alle Profile + profile dropdown + + + All reports will be archived for you. + Alle Meldungen werden für Sie archiviert. + No comment provided by engineer. + + + All servers + Alle Server No comment provided by engineer. @@ -800,6 +876,11 @@ Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden) No comment provided by engineer. + + Allow to report messsages to moderators. + Melden von Nachrichten an Moderatoren erlauben. + No comment provided by engineer. + Allow to send SimpleX links. Das Senden von SimpleX-Links erlauben. @@ -880,11 +961,21 @@ Es wurde ein leeres Chat-Profil mit dem eingegebenen Namen erstellt und die App öffnet wie gewohnt. No comment provided by engineer. + + Another reason + Anderer Grund + report reason + 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: %@ @@ -900,6 +991,11 @@ Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt. No comment provided by engineer. + + App group: + App-Gruppe: + No comment provided by engineer. + App icon App-Icon @@ -915,6 +1011,11 @@ App-Zugangscode wurde durch den Selbstzerstörungs-Zugangscode ersetzt. No comment provided by engineer. + + App session + App-Sitzung + No comment provided by engineer. + App version App Version @@ -940,6 +1041,21 @@ Anwenden auf No comment provided by engineer. + + Archive + Archiv + No comment provided by engineer. + + + Archive %lld reports? + Archiviere %lld Meldungen? + No comment provided by engineer. + + + Archive all reports? + Alle Meldungen archivieren? + No comment provided by engineer. + Archive and upload Archivieren und Hochladen @@ -950,6 +1066,21 @@ Kontakte für spätere Chats archivieren. No comment provided by engineer. + + Archive report + Meldung archivieren + No comment provided by engineer. + + + Archive report? + Meldung archivieren? + No comment provided by engineer. + + + Archive reports + Meldungen archivieren + swipe action + Archived contacts Archivierte Kontakte @@ -1020,6 +1151,11 @@ Bilder automatisch akzeptieren No comment provided by engineer. + + Auto-accept settings + Einstellungen automatisch akzeptieren + alert title + Back Zurück @@ -1045,11 +1181,26 @@ Ungültiger Nachrichten-Hash No comment provided by engineer. + + Better calls + Verbesserte Anrufe + No comment provided by engineer. + Better groups Bessere Gruppen No comment provided by engineer. + + Better groups performance + Bessere Leistung von Gruppen + No comment provided by engineer. + + + Better message dates. + Verbesserte Nachrichten-Datumsinformation + No comment provided by engineer. + Better messages Verbesserungen bei Nachrichten @@ -1060,6 +1211,26 @@ Kontrollieren Sie Ihr Netzwerk No comment provided by engineer. + + Better notifications + Verbesserte Benachrichtigungen + No comment provided by engineer. + + + Better privacy and security + Bessere(r) Security und Datenschutz + No comment provided by engineer. + + + Better security ✅ + Verbesserte Sicherheit ✅ + No comment provided by engineer. + + + Better user experience + Verbesserte Nutzer-Erfahrung + No comment provided by engineer. + Black Schwarz @@ -1107,7 +1278,7 @@ Blur media - Medium unscharf machen + Medium verpixeln No comment provided by engineer. @@ -1140,11 +1311,35 @@ Bulgarisch, Finnisch, Thailändisch und Ukrainisch - Dank der Nutzer und [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Geschäftliche Adresse + No comment provided by engineer. + + + Business chats + Geschäftliche Chats + No comment provided by engineer. + + + Businesses + Unternehmen + 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: +- 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! @@ -1193,7 +1388,8 @@ Cancel Abbrechen - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1409,7 @@ Cannot receive file Datei kann nicht empfangen werden - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1426,16 @@ Ändern No comment provided by engineer. + + Change automatic message deletion? + Automatisches Löschen von Nachrichten ändern? + alert title + + + Change chat profiles + Chat-Profile wechseln + authentication reason + Change database passphrase? Datenbank-Passwort ändern? @@ -1274,11 +1480,21 @@ Change self-destruct passcode Selbstzerstörungs-Zugangscode ändern authentication reason - set passcode view +set passcode view - - Chat archive - Datenbank Archiv + + Chat + Chat + No comment provided by engineer. + + + Chat already exists + Chat besteht bereits + No comment provided by engineer. + + + Chat already exists! + Chat besteht bereits! No comment provided by engineer. @@ -1341,20 +1557,50 @@ Chat-Präferenzen No comment provided by engineer. + + Chat preferences were changed. + Die Chat-Präferenzen wurden geändert. + alert message + + + Chat profile + Benutzerprofil + No comment provided by engineer. + Chat theme Chat-Design No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + Der Chat wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden! + 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! + No comment provided by engineer. + Chats Chats 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. + No comment provided by engineer. + Check server address and try again. Überprüfen Sie die Serveradresse und versuchen Sie es nochmal. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1393,22 +1639,32 @@ Clear - Löschen + Entfernen swipe action Clear conversation - Chatinhalte löschen + Chat-Inhalte entfernen No comment provided by engineer. Clear conversation? - Unterhaltung löschen? + Chat-Inhalte entfernen? + No comment provided by engineer. + + + Clear group? + Gruppe entfernen? + No comment provided by engineer. + + + Clear or delete group? + Gruppe entfernen oder löschen? No comment provided by engineer. Clear private notes? - Private Notizen löschen? + Private Notizen entfernen? No comment provided by engineer. @@ -1426,6 +1682,11 @@ Farbvariante No comment provided by engineer. + + Community guidelines violation + Verstoß gegen die Gemeinschaftsrichtlinien + report reason + Compare file Datei vergleichen @@ -1441,14 +1702,49 @@ Abgeschlossen No comment provided by engineer. + + Conditions accepted on: %@. + Die Nutzungsbedingungen wurden akzeptiert am: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**. + No comment provided by engineer. + + + Conditions of use + Nutzungsbedingungen + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Die Nutzungsbedingungen werden akzeptiert am: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Die Nutzungsbedingungen der aktivierten Betreiber werden automatisch akzeptiert am: %@. + No comment provided by engineer. + Configure ICE servers ICE-Server konfigurieren No comment provided by engineer. - - Configured %@ servers - Konfigurierte %@ Server + + Configure server operators + Server-Betreiber konfigurieren No comment provided by engineer. @@ -1501,6 +1797,11 @@ Hochladen bestätigen No comment provided by engineer. + + Confirmed + Bestätigt + token status text + Connect Verbinden @@ -1620,6 +1921,11 @@ Das ist Ihr eigener Einmal-Link! Verbindungs- und Server-Status. No comment provided by engineer. + + Connection blocked + Verbindung blockiert + No comment provided by engineer. + Connection error Verbindungsfehler @@ -1630,6 +1936,18 @@ Das ist Ihr eigener Einmal-Link! Verbindungsfehler (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + Die Verbindung wurde vom Server-Betreiber blockiert: +%@ + No comment provided by engineer. + + + Connection not ready. + Verbindung noch nicht bereit. + No comment provided by engineer. + Connection notifications Verbindungsbenachrichtigungen @@ -1640,6 +1958,16 @@ Das ist Ihr eigener Einmal-Link! Verbindungsanfrage wurde gesendet! No comment provided by engineer. + + Connection requires encryption renegotiation. + Die Verbindung erfordert eine Neuverhandlung der Verschlüsselung. + No comment provided by engineer. + + + Connection security + Verbindungs-Sicherheit + No comment provided by engineer. + Connection terminated Verbindung beendet @@ -1715,6 +2043,11 @@ Das ist Ihr eigener Einmal-Link! Ihre Kontakte können Nachrichten zum Löschen markieren. Sie können diese Nachrichten trotzdem anschauen. No comment provided by engineer. + + Content violates conditions of use + Inhalt verletzt Nutzungsbedingungen + blocking reason + Continue Weiter @@ -1722,7 +2055,7 @@ Das ist Ihr eigener Einmal-Link! Conversation deleted! - Unterhaltung gelöscht! + Chat-Inhalte entfernt! No comment provided by engineer. @@ -1740,6 +2073,11 @@ Das ist Ihr eigener Einmal-Link! Core Version: v%@ No comment provided by engineer. + + Corner + Abrundung Ecken + No comment provided by engineer. + Correct name to %@? Richtiger Name für %@? @@ -1750,6 +2088,11 @@ Das ist Ihr eigener Einmal-Link! Erstellen No comment provided by engineer. + + Create 1-time link + Einmal-Link erstellen + No comment provided by engineer. + Create SimpleX address SimpleX-Adresse erstellen @@ -1760,11 +2103,6 @@ Das ist Ihr eigener Einmal-Link! Erstellen Sie eine Gruppe mit einem zufälligen Profil. No comment provided by engineer. - - Create an address to let people connect with you. - Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können. - No comment provided by engineer. - Create file Datei erstellen @@ -1785,6 +2123,11 @@ Das ist Ihr eigener Einmal-Link! Link erzeugen No comment provided by engineer. + + Create list + Liste erstellen + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Neues Profil in der [Desktop-App] erstellen (https://simplex.chat/downloads/). 💻 @@ -1825,11 +2168,6 @@ Das ist Ihr eigener Einmal-Link! Erstellt um: %@ copied message info - - Created on %@ - Erstellt am %@ - No comment provided by engineer. - Creating archive link Archiv-Link erzeugen @@ -1845,6 +2183,11 @@ Das ist Ihr eigener Einmal-Link! Aktueller Zugangscode No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Der Text der aktuellen Nutzungsbedingungen konnte nicht geladen werden. Sie können die Nutzungsbedingungen unter diesem Link einsehen: + No comment provided by engineer. + Current passphrase… Aktuelles Passwort… @@ -1852,7 +2195,7 @@ Das ist Ihr eigener Einmal-Link! Current profile - Aktueller Profil + Aktuelles Profil No comment provided by engineer. @@ -1865,6 +2208,11 @@ Das ist Ihr eigener Einmal-Link! Zeit anpassen No comment provided by engineer. + + Customizable message shape. + Anpassbares Format des Nachrichtenfelds + No comment provided by engineer. + Customize theme Design anpassen @@ -1996,8 +2344,8 @@ Das ist Ihr eigener Einmal-Link! Delete Löschen - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2382,14 @@ Das ist Ihr eigener Einmal-Link! Kontakt löschen und benachrichtigen No comment provided by engineer. - - Delete archive - Archiv löschen + + Delete chat + Chat löschen No comment provided by engineer. - - Delete chat archive? - Chat Archiv löschen? + + Delete chat messages from your device. + Chat-Nachrichten von Ihrem Gerät löschen. No comment provided by engineer. @@ -2054,6 +2402,11 @@ Das ist Ihr eigener Einmal-Link! Chat-Profil löschen? No comment provided by engineer. + + Delete chat? + Chat löschen? + No comment provided by engineer. + Delete connection Verbindung löschen @@ -2129,6 +2482,11 @@ Das ist Ihr eigener Einmal-Link! Link löschen? No comment provided by engineer. + + Delete list? + Liste löschen? + alert title + Delete member message? Nachricht des Mitglieds löschen? @@ -2142,11 +2500,11 @@ Das ist Ihr eigener Einmal-Link! Delete messages Nachrichten löschen - No comment provided by engineer. + alert button Delete messages after - Löschen der Nachrichten + Nachrichten löschen No comment provided by engineer. @@ -2159,6 +2517,11 @@ Das ist Ihr eigener Einmal-Link! Alte Datenbank löschen? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Bis zu 200 Nachrichten löschen oder moderieren + No comment provided by engineer. + Delete pending connection? Ausstehende Verbindung löschen? @@ -2174,6 +2537,11 @@ Das ist Ihr eigener Einmal-Link! Lösche Warteschlange server test step + + Delete report + Meldung löschen + No comment provided by engineer. + Delete up to 20 messages at once. Löschen Sie bis zu 20 Nachrichten auf einmal. @@ -2209,6 +2577,11 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Löschen No comment provided by engineer. + + Delivered even when Apple drops them. + Auslieferung, selbst wenn Apple sie löscht. + No comment provided by engineer. + Delivery Zustellung @@ -2309,8 +2682,13 @@ Das ist Ihr eigener Einmal-Link! Direkte Nachrichten chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + In diesem Chat sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. + 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. @@ -2324,6 +2702,16 @@ Das ist Ihr eigener Einmal-Link! SimpleX-Sperre deaktivieren authentication reason + + Disable automatic message deletion? + Automatisches Löschen von Nachrichten deaktivieren? + alert title + + + Disable delete messages + Löschen von Nachrichten deaktivieren + alert button + Disable for all Für Alle deaktivieren @@ -2349,8 +2737,8 @@ Das ist Ihr eigener Einmal-Link! In diesem Chat sind verschwindende Nachrichten nicht erlaubt. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt. No comment provided by engineer. @@ -2409,6 +2797,16 @@ Das ist Ihr eigener Einmal-Link! Den Nachrichtenverlauf nicht an neue Mitglieder senden. No comment provided by engineer. + + Do not use credentials with proxy. + Verwenden Sie keine Anmeldeinformationen mit einem Proxy. + No comment provided by engineer. + + + Documents: + Dokumente: + No comment provided by engineer. + Don't create address Keine Adresse erstellt @@ -2419,11 +2817,21 @@ Das ist Ihr eigener Einmal-Link! Nicht aktivieren No comment provided by engineer. + + Don't miss important messages. + Verpassen Sie keine wichtigen Nachrichten. + No comment provided by engineer. + Don't show again Nicht nochmals anzeigen No comment provided by engineer. + + Done + Fertig + No comment provided by engineer. + Downgrade and open chat Datenbank herabstufen und den Chat öffnen @@ -2432,7 +2840,8 @@ Das ist Ihr eigener Einmal-Link! Download Herunterladen - chat item action + alert button +chat item action Download errors @@ -2449,6 +2858,11 @@ Das ist Ihr eigener Einmal-Link! Datei herunterladen server test step + + Download files + Dateien herunterladen + alert action + Downloaded Heruntergeladen @@ -2479,6 +2893,11 @@ Das ist Ihr eigener Einmal-Link! Dauer No comment provided by engineer. + + E2E encrypted notifications. + E2E-verschlüsselte Benachrichtigungen. + No comment provided by engineer. + Edit Bearbeiten @@ -2499,6 +2918,11 @@ Das ist Ihr eigener Einmal-Link! Aktivieren (vorgenommene Einstellungen bleiben erhalten) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + Für einen besseren Metadatenschutz Flux in den Netzwerk- und Servereinstellungen aktivieren. + No comment provided by engineer. + Enable SimpleX Lock SimpleX-Sperre aktivieren @@ -2512,7 +2936,7 @@ Das ist Ihr eigener Einmal-Link! Enable automatic message deletion? Automatisches Löschen von Nachrichten aktivieren? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3063,11 @@ Das ist Ihr eigener Einmal-Link! Neuverhandlung der Verschlüsselung fehlgeschlagen. No comment provided by engineer. + + Encryption renegotiation in progress. + Die Neuverhandlung der Verschlüsselung läuft. + No comment provided by engineer. + Enter Passcode Zugangscode eingeben @@ -2704,26 +3133,36 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beenden des Adresswechsels No comment provided by engineer. + + Error accepting conditions + Fehler beim Akzeptieren der Nutzungsbedingungen + alert title + Error accepting contact request Fehler beim Annehmen der Kontaktanfrage No comment provided by engineer. - - Error accessing database file - Fehler beim Zugriff auf die Datenbankdatei - No comment provided by engineer. - Error adding member(s) Fehler beim Hinzufügen von Mitgliedern No comment provided by engineer. + + Error adding server + Fehler beim Hinzufügen des Servers + alert title + Error changing address Fehler beim Wechseln der Empfängeradresse No comment provided by engineer. + + Error changing connection profile + Fehler beim Wechseln des Verbindungs-Profils + No comment provided by engineer. + Error changing role Fehler beim Ändern der Rolle @@ -2734,6 +3173,16 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Ändern der Einstellung No comment provided by engineer. + + Error changing to incognito! + Fehler beim Wechseln zum Inkognito-Profil! + No comment provided by engineer. + + + Error checking token status + Fehler beim Überprüfen des Token-Status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut. @@ -2754,6 +3203,11 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Erzeugen des Gruppen-Links No comment provided by engineer. + + Error creating list + Fehler beim Erstellen der Liste + alert title + Error creating member contact Fehler beim Anlegen eines Mitglied-Kontaktes @@ -2769,6 +3223,11 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Erstellen des Profils! No comment provided by engineer. + + Error creating report + Fehler beim Erstellen der Meldung + No comment provided by engineer. + Error decrypting file Fehler beim Entschlüsseln der Datei @@ -2849,9 +3308,14 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beitritt zur Gruppe No comment provided by engineer. - - Error loading %@ servers - Fehler beim Laden von %@ Servern + + Error loading servers + Fehler beim Laden der Server + alert title + + + Error migrating settings + Fehler beim Migrieren der Einstellungen No comment provided by engineer. @@ -2861,8 +3325,8 @@ Das ist Ihr eigener Einmal-Link! Error receiving file - Fehler beim Empfangen der Datei - No comment provided by engineer. + Fehler beim Herunterladen der Datei + alert title Error reconnecting server @@ -2874,26 +3338,36 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Wiederherstellen der Verbindungen zu den Servern No comment provided by engineer. + + Error registering for notifications + Fehler beim Registrieren für Benachrichtigungen + alert title + Error removing member Fehler beim Entfernen des Mitglieds No comment provided by engineer. + + Error reordering lists + Fehler beim Umsortieren der Listen + alert title + Error resetting statistics Fehler beim Zurücksetzen der Statistiken No comment provided by engineer. - - Error saving %@ servers - Fehler beim Speichern der %@-Server - No comment provided by engineer. - Error saving ICE servers Fehler beim Speichern der ICE-Server No comment provided by engineer. + + Error saving chat list + Fehler beim Speichern der Chat-Liste + alert title + Error saving group profile Fehler beim Speichern des Gruppenprofils @@ -2909,6 +3383,11 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Speichern des Passworts in den Schlüsselbund No comment provided by engineer. + + Error saving servers + Fehler beim Speichern der Server + alert title + Error saving settings Fehler beim Abspeichern der Einstellungen @@ -2954,16 +3433,26 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beenden des Chats No comment provided by engineer. + + Error switching profile + Fehler beim Wechseln des Profils + No comment provided by engineer. + Error switching profile! Fehler beim Umschalten des Profils! - No comment provided by engineer. + alertTitle Error synchronizing connection Fehler beim Synchronisieren der Verbindung No comment provided by engineer. + + Error testing server connection + Fehler beim Testen der Server-Verbindung + No comment provided by engineer. + Error updating group link Fehler beim Aktualisieren des Gruppen-Links @@ -2974,6 +3463,11 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Aktualisieren der Nachricht No comment provided by engineer. + + Error updating server + Fehler beim Aktualisieren des Servers + alert title + Error updating settings Fehler beim Aktualisieren der Einstellungen @@ -3002,8 +3496,9 @@ Das ist Ihr eigener Einmal-Link! Error: %@ Fehler: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3515,11 @@ Das ist Ihr eigener Einmal-Link! Fehler No comment provided by engineer. + + Errors in servers configuration. + Fehler in der Server-Konfiguration. + servers error + Even when disabled in the conversation. Auch wenn sie im Chat deaktiviert sind. @@ -3035,6 +3535,11 @@ Das ist Ihr eigener Einmal-Link! Erweitern chat item action + + Expired + Abgelaufen + token status text + Export database Datenbank exportieren @@ -3075,20 +3580,49 @@ Das ist Ihr eigener Einmal-Link! Schnell und ohne warten auf den Absender, bis er online ist! No comment provided by engineer. + + Faster deletion of groups. + Schnelleres löschen von Gruppen. + No comment provided by engineer. + Faster joining and more reliable messages. Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung. No comment provided by engineer. + + Faster sending messages. + Schnelleres versenden von Nachrichten. + No comment provided by engineer. + Favorite Favorit swipe action + + Favorites + Favoriten + No comment provided by engineer. + File error Datei-Fehler - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Datei-Fehler: +%@ + alert message + + + File is blocked by server operator: +%@. + Datei wurde vom Server-Betreiber blockiert: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3117,12 +3651,12 @@ Das ist Ihr eigener Einmal-Link! File will be received when your contact completes uploading it. - Die Datei wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. + Die Datei wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. No comment provided by engineer. File will be received when your contact is online, please wait or check later! - Die Datei wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! + Die Datei wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! No comment provided by engineer. @@ -3145,8 +3679,8 @@ Das ist Ihr eigener Einmal-Link! Dateien und Medien chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. In dieser Gruppe sind Dateien und Medien nicht erlaubt. No comment provided by engineer. @@ -3215,21 +3749,71 @@ Das ist Ihr eigener Einmal-Link! Reparatur wird vom Gruppenmitglied nicht unterstützt No comment provided by engineer. + + For all moderators + Für alle Moderatoren + No comment provided by engineer. + + + For chat profile %@: + Für das Chat-Profil %@: + servers error + For console Für Konsole No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Wenn Ihr Kontakt beispielsweise Nachrichten über einen SimpleX-Chatserver empfängt, wird Ihre App diese über einen der Server von Flux versenden. + No comment provided by engineer. + + + For me + Für mich + No comment provided by engineer. + + + For private routing + Für privates Routing + No comment provided by engineer. + + + For social media + Für soziale Medien + No comment provided by engineer. + Forward Weiterleiten chat item action + + Forward %d message(s)? + %d Nachricht(en) weiterleiten? + alert title + Forward and save messages Nachrichten weiterleiten und speichern No comment provided by engineer. + + Forward messages + Nachrichten weiterleiten + alert action + + + Forward messages without files? + Nachrichten ohne Dateien weiterleiten? + alert message + + + Forward up to 20 messages at once. + Bis zu 20 Nachrichten auf einmal weiterleiten + No comment provided by engineer. + Forwarded Weitergeleitet @@ -3240,6 +3824,11 @@ Das ist Ihr eigener Einmal-Link! Weitergeleitet aus No comment provided by engineer. + + Forwarding %lld messages + %lld Nachricht(en) wird/werden weitergeleitet + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut. @@ -3289,11 +3878,6 @@ Fehler: %2$@ Vollständiger Name (optional) No comment provided by engineer. - - Full name: - Vollständiger Name: - No comment provided by engineer. - Fully decentralized – visible only to members. Vollständig dezentralisiert – nur für Mitglieder sichtbar. @@ -3314,6 +3898,11 @@ Fehler: %2$@ GIFs und Sticker No comment provided by engineer. + + Get notified when mentioned. + Bei Erwähnung benachrichtigt werden. + No comment provided by engineer. + Good afternoon! Guten Nachmittag! @@ -3379,41 +3968,6 @@ Fehler: %2$@ Gruppen-Links No comment provided by engineer. - - Group members can add message reactions. - Gruppenmitglieder können eine Reaktion auf Nachrichten geben. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) - No comment provided by engineer. - - - Group members can send SimpleX links. - Gruppenmitglieder können SimpleX-Links senden. - No comment provided by engineer. - - - Group members can send direct messages. - Gruppenmitglieder können Direktnachrichten versenden. - No comment provided by engineer. - - - Group members can send disappearing messages. - Gruppenmitglieder können verschwindende Nachrichten senden. - No comment provided by engineer. - - - Group members can send files and media. - Gruppenmitglieder können Dateien und Medien senden. - No comment provided by engineer. - - - Group members can send voice messages. - Gruppenmitglieder können Sprachnachrichten versenden. - No comment provided by engineer. - Group message: Grppennachricht: @@ -3451,7 +4005,12 @@ Fehler: %2$@ Group will be deleted for you - this cannot be undone! - Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! + Die Gruppe wird nur bei Ihnen gelöscht. Dies kann nicht rückgängig gemacht werden! + No comment provided by engineer. + + + Groups + Gruppen No comment provided by engineer. @@ -3459,6 +4018,11 @@ Fehler: %2$@ Hilfe No comment provided by engineer. + + Help admins moderating their groups. + Helfen Sie Administratoren bei der Moderation ihrer Gruppen. + No comment provided by engineer. + Hidden Verborgen @@ -3509,10 +4073,20 @@ Fehler: %2$@ Wie SimpleX funktioniert No comment provided by engineer. + + How it affects privacy + Wie es die Privatsphäre beeinflusst + No comment provided by engineer. + + + How it helps privacy + Wie es die Privatsphäre schützt + No comment provided by engineer. + How it works Wie es funktioniert - No comment provided by engineer. + alert button How to @@ -3539,6 +4113,11 @@ Fehler: %2$@ ICE-Server (einer pro Zeile) No comment provided by engineer. + + IP address + IP-Adresse + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Falls Sie sich nicht persönlich treffen können, zeigen Sie den QR-Code in einem Videoanruf oder teilen Sie den Link. @@ -3566,12 +4145,12 @@ Fehler: %2$@ Image will be received when your contact completes uploading it. - Das Bild wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. + Das Bild wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. No comment provided by engineer. Image will be received when your contact is online, please wait or check later! - Das Bild wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! + Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! No comment provided by engineer. @@ -3579,8 +4158,8 @@ Fehler: %2$@ Sofort No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immun gegen Spam und Missbrauch No comment provided by engineer. @@ -3614,6 +4193,13 @@ Fehler: %2$@ Archiv wird importiert No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch. +Weitere Verbesserungen sind bald verfügbar! + No comment provided by engineer. + Improved message delivery Verbesserte Zustellung von Nachrichten @@ -3644,6 +4230,16 @@ Fehler: %2$@ Klingeltöne No comment provided by engineer. + + Inappropriate content + Unangemessener Inhalt + report reason + + + Inappropriate profile + Unangemessenes Profil + report reason + Incognito Inkognito @@ -3714,6 +4310,11 @@ Fehler: %2$@ Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Sofort + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4322,6 @@ Fehler: %2$@ No comment provided by engineer. - - Instantly - Sofort - No comment provided by engineer. - Interface Schnittstelle @@ -3736,6 +4332,31 @@ Fehler: %2$@ Interface-Farben No comment provided by engineer. + + Invalid + Ungültig + token status text + + + Invalid (bad token) + Ungültig (falsches Token) + token status text + + + Invalid (expired) + Ungültig (abgelaufen) + token status text + + + Invalid (unregistered) + Ungültig (nicht registriert) + token status text + + + Invalid (wrong topic) + Ungültig (falsches Thema) + token status text + Invalid QR code Ungültiger QR-Code @@ -3774,7 +4395,7 @@ Fehler: %2$@ Invalid server address! Ungültige Serveradresse! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4417,11 @@ Fehler: %2$@ Mitglieder einladen No comment provided by engineer. + + Invite to chat + Zum Chat einladen + No comment provided by engineer. + Invite to group In Gruppe einladen @@ -3811,8 +4437,8 @@ Fehler: %2$@ In diesem Chat ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt. No comment provided by engineer. @@ -3902,11 +4528,11 @@ Das ist Ihr Link für die Gruppe %@! Keep Behalten - No comment provided by engineer. + alert action Keep conversation - Unterhaltung behalten + Chat-Inhalte beibehalten No comment provided by engineer. @@ -3917,7 +4543,7 @@ Das ist Ihr Link für die Gruppe %@! Keep unused invitation? Nicht genutzte Einladung behalten? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4580,16 @@ Das ist Ihr Link für die Gruppe %@! Verlassen swipe action + + Leave chat + Chat verlassen + No comment provided by engineer. + + + Leave chat? + Chat verlassen? + No comment provided by engineer. + Leave group Gruppe verlassen @@ -3994,6 +4630,21 @@ Das ist Ihr Link für die Gruppe %@! Verknüpfte Desktops No comment provided by engineer. + + List + Liste + swipe action + + + List name and emoji should be different for all lists. + Der Listenname und das Emoji sollen für alle Listen unterschiedlich sein. + No comment provided by engineer. + + + List name... + Listenname... + No comment provided by engineer. + Live message! Live Nachricht! @@ -4004,11 +4655,6 @@ Das ist Ihr Link für die Gruppe %@! Live Nachrichten No comment provided by engineer. - - Local - Lokal - No comment provided by engineer. - Local name Lokaler Name @@ -4029,11 +4675,6 @@ Das ist Ihr Link für die Gruppe %@! Sperr-Modus No comment provided by engineer. - - Make a private connection - Stellen Sie eine private Verbindung her - No comment provided by engineer. - Make one message disappear Eine verschwindende Nachricht verfassen @@ -4044,21 +4685,11 @@ Das ist Ihr Link für die Gruppe %@! Privates Profil erzeugen! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Stellen Sie sicher, dass die %@-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?* - No comment provided by engineer. - Mark deleted for everyone Für Alle als gelöscht markieren @@ -4104,6 +4735,16 @@ Das ist Ihr Link für die Gruppe %@! Mitglied inaktiv item status text + + Member reports + Mitglieder-Meldungen + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Die Rolle des Mitglieds wird auf "%@" geändert. Alle Chat-Mitglieder werden darüber informiert. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Die Mitgliederrolle wird auf "%@" geändert. Alle Mitglieder der Gruppe werden benachrichtigt. @@ -4114,11 +4755,61 @@ Das ist Ihr Link für die Gruppe %@! Die Mitgliederrolle wird auf "%@" geändert. Das Mitglied wird eine neue Einladung erhalten. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden! 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 irreversibly delete sent messages. (24 hours) + Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) + No comment provided by engineer. + + + Members can report messsages to moderators. + Mitglieder können Nachrichten an Moderatoren melden. + No comment provided by engineer. + + + Members can send SimpleX links. + Gruppenmitglieder können SimpleX-Links versenden. + No comment provided by engineer. + + + Members can send direct messages. + Gruppenmitglieder können Direktnachrichten versenden. + No comment provided by engineer. + + + Members can send disappearing messages. + Gruppenmitglieder können verschwindende Nachrichten versenden. + No comment provided by engineer. + + + Members can send files and media. + Gruppenmitglieder können Dateien und Medien versenden. + No comment provided by engineer. + + + Members can send voice messages. + Gruppenmitglieder können Sprachnachrichten versenden. + No comment provided by engineer. + + + Mention members 👋 + Erwähnung von Mitgliedern 👋 + No comment provided by engineer. + Menus Menüs @@ -4169,8 +4860,8 @@ Das ist Ihr Link für die Gruppe %@! In diesem Chat sind Reaktionen auf Nachrichten nicht erlaubt. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt. No comment provided by engineer. @@ -4184,6 +4875,11 @@ Das ist Ihr Link für die Gruppe %@! Nachrichten-Server No comment provided by engineer. + + Message shape + Nachrichten-Form + No comment provided by engineer. + Message source remains private. Die Nachrichtenquelle bleibt privat. @@ -4224,6 +4920,11 @@ Das ist Ihr Link für die Gruppe %@! Die Nachrichten von %@ werden angezeigt! No comment provided by engineer. + + Messages in this chat will never be deleted. + Nachrichten in diesem Chat werden nie gelöscht. + alert message + Messages received Empfangene Nachrichten @@ -4234,14 +4935,19 @@ Das ist Ihr Link für die Gruppe %@! Gesendete Nachrichten No comment provided by engineer. + + Messages were deleted after you selected them. + Die Nachrichten wurden gelöscht, nachdem Sie sie ausgewählt hatten. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. + Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt. 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. - Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. + 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. @@ -4299,9 +5005,9 @@ Das ist Ihr Link für die Gruppe %@! Die Migration wurde abgeschlossen No comment provided by engineer. - - Migrations: %@ - Migrationen: %@ + + Migrations: + Migrationen: No comment provided by engineer. @@ -4319,6 +5025,11 @@ Das ist Ihr Link für die Gruppe %@! Moderiert um: %@ copied message info + + More + Mehr + swipe action + More improvements are coming soon! Weitere Verbesserungen sind bald verfügbar! @@ -4329,6 +5040,11 @@ Das ist Ihr Link für die Gruppe %@! Zuverlässigere Netzwerkverbindung. No comment provided by engineer. + + More reliable notifications + Zuverlässigere Benachrichtigungen + No comment provided by engineer. + Most likely this connection is deleted. Wahrscheinlich ist diese Verbindung gelöscht worden. @@ -4342,7 +5058,12 @@ Das ist Ihr Link für die Gruppe %@! Mute Stummschalten - swipe action + notification label action + + + Mute all + Alle stummschalten + notification label action Muted when inactive! @@ -4364,6 +5085,11 @@ Das ist Ihr Link für die Gruppe %@! Netzwerkverbindung No comment provided by engineer. + + Network decentralization + Dezentralisiertes Netzwerk + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen. @@ -4374,6 +5100,11 @@ Das ist Ihr Link für die Gruppe %@! Netzwerk-Verwaltung No comment provided by engineer. + + Network operator + Netzwerk-Betreiber + No comment provided by engineer. + Network settings Netzwerkeinstellungen @@ -4384,11 +5115,26 @@ Das ist Ihr Link für die Gruppe %@! Netzwerkstatus No comment provided by engineer. + + New + Neu + token status text + New Passcode Neuer Zugangscode No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Jedes Mal wenn Sie die App starten, werden neue SOCKS-Anmeldeinformationen genutzt + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Für jeden Server werden neue SOCKS-Anmeldeinformationen genutzt + No comment provided by engineer. + New chat Neuer Chat @@ -4409,11 +5155,6 @@ Das ist Ihr Link für die Gruppe %@! Neuer Kontakt: notification - - New database archive - Neues Datenbankarchiv - No comment provided by engineer. - New desktop app! Neue Desktop-App! @@ -4424,6 +5165,11 @@ Das ist Ihr Link für die Gruppe %@! Neuer Anzeigename No comment provided by engineer. + + New events + Neue Ereignisse + notification + New in %@ Neu in %@ @@ -4449,6 +5195,11 @@ Das ist Ihr Link für die Gruppe %@! Neues Passwort… No comment provided by engineer. + + New server + Neuer Server + No comment provided by engineer. + No Nein @@ -4459,6 +5210,21 @@ Das ist Ihr Link für die Gruppe %@! Kein App-Passwort Authentication unavailable + + No chats + Keine Chats + No comment provided by engineer. + + + No chats found + Keine Chats gefunden + No comment provided by engineer. + + + No chats in list %@ + Keine Chats in der Liste %@ + No comment provided by engineer. + No contacts selected Keine Kontakte ausgewählt @@ -4504,19 +5270,84 @@ Das ist Ihr Link für die Gruppe %@! Keine Information - es wird versucht neu zu laden No comment provided by engineer. + + No media & file servers. + Keine Medien- und Dateiserver. + servers error + + + No message + Keine Nachricht + No comment provided by engineer. + + + No message servers. + Keine Nachrichten-Server. + servers error + No network connection Keine Netzwerkverbindung No comment provided by engineer. + + No permission to record speech + Keine Genehmigung für Sprach-Aufnahmen + No comment provided by engineer. + + + No permission to record video + Keine Genehmigung für Video-Aufnahmen + No comment provided by engineer. + No permission to record voice message Keine Berechtigung für das Aufnehmen von Sprachnachrichten No comment provided by engineer. + + No push server + Lokal + No comment provided by engineer. + No received or sent files - Keine empfangenen oder gesendeten Dateien + Keine herunter- oder hochgeladenen Dateien + No comment provided by engineer. + + + No servers for private message routing. + Keine Server für privates Nachrichten-Routing. + servers error + + + No servers to receive files. + Keine Server für das Herunterladen von Dateien. + servers error + + + No servers to receive messages. + Keine Server für den Empfang von Nachrichten. + servers error + + + No servers to send files. + Keine Server für das Versenden von Dateien. + servers error + + + No token! + Kein Token! + alert title + + + No unread chats + Keine ungelesenen Chats + No comment provided by engineer. + + + No user identifiers. + Keine Benutzerkennungen. No comment provided by engineer. @@ -4524,11 +5355,21 @@ Das ist Ihr Link für die Gruppe %@! Nicht kompatibel! No comment provided by engineer. + + Notes + Anmerkungen + No comment provided by engineer. + Nothing selected Nichts ausgewählt No comment provided by engineer. + + Nothing to forward! + Es gibt nichts zum Weiterleiten! + alert title + Notifications Benachrichtigungen @@ -4539,6 +5380,21 @@ Das ist Ihr Link für die Gruppe %@! Benachrichtigungen sind deaktiviert! No comment provided by engineer. + + Notifications error + Benachrichtigungs-Fehler + alert title + + + Notifications privacy + Datenschutz für Benachrichtigungen + No comment provided by engineer. + + + Notifications status + Benachrichtigungs-Status + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5417,13 @@ Das ist Ihr Link für die Gruppe %@! Ok Ok - No comment provided by engineer. + alert button Old database Alte Datenbank No comment provided by engineer. - - Old database archive - Altes Datenbankarchiv - No comment provided by engineer. - One-time invitation link Einmal-Einladungslink @@ -4597,14 +5448,19 @@ Dies erfordert die Aktivierung eines VPNs. Onion-Hosts werden nicht verwendet. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Nur Chat-Eigentümer können die Präferenzen ändern. + 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. No comment provided by engineer. Only delete conversation - Nur die Unterhaltung löschen + Nur die Chat-Inhalte löschen No comment provided by engineer. @@ -4622,6 +5478,16 @@ Dies erfordert die Aktivierung eines VPNs. Sprachnachrichten können nur von Gruppen-Eigentümern aktiviert werden. No comment provided by engineer. + + Only sender and moderators see it + Nur Absender und Moderatoren sehen es + No comment provided by engineer. + + + Only you and moderators see it + Nur Sie und Moderatoren sehen es + No comment provided by engineer. + Only you can add message reactions. Nur Sie können Reaktionen auf Nachrichten geben. @@ -4675,13 +5541,18 @@ Dies erfordert die Aktivierung eines VPNs. Open Öffnen - No comment provided by engineer. + alert action Open Settings Geräte-Einstellungen öffnen No comment provided by engineer. + + Open changes + Änderungen öffnen + No comment provided by engineer. + Open chat Chat öffnen @@ -4692,36 +5563,45 @@ Dies erfordert die Aktivierung eines VPNs. Chat-Konsole öffnen authentication reason + + Open conditions + Nutzungsbedingungen öffnen + No comment provided by engineer. + Open group Gruppe öffnen No comment provided by engineer. + + Open link? + alert title + Open migration to another device Migration auf ein anderes Gerät öffnen authentication reason - - Open server settings - Server-Einstellungen öffnen - No comment provided by engineer. - - - Open user profiles - Benutzerprofile öffnen - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen. - No comment provided by engineer. - Opening app… App wird geöffnet… No comment provided by engineer. + + Operator + Betreiber + No comment provided by engineer. + + + Operator server + Betreiber-Server + alert title + + + Or import archive file + Oder importieren Sie eine Archiv-Datei + No comment provided by engineer. + Or paste archive link Oder fügen Sie den Archiv-Link ein @@ -4742,15 +5622,27 @@ Dies erfordert die Aktivierung eines VPNs. Oder diesen QR-Code anzeigen No comment provided by engineer. + + Or to share privately + Oder zum privaten Teilen + No comment provided by engineer. + + + Organize chats into lists + Chats in Listen verwalten + No comment provided by engineer. + Other Andere No comment provided by engineer. - - Other %@ servers - Andere %@ Server - No comment provided by engineer. + + Other file errors: +%@ + Andere(r) Datei-Fehler: +%@ + alert message PING count @@ -4787,6 +5679,11 @@ Dies erfordert die Aktivierung eines VPNs. Zugangscode eingestellt! No comment provided by engineer. + + Password + Passwort + No comment provided by engineer. + Password to show Passwort anzeigen @@ -4822,13 +5719,8 @@ Dies erfordert die Aktivierung eines VPNs. Ausstehend No comment provided by engineer. - - People can connect to you only via the links you share. - Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen. - No comment provided by engineer. - - - Periodically + + Periodic Periodisch No comment provided by engineer. @@ -4931,11 +5823,31 @@ Fehler: %@ Bitte bewahren Sie das Passwort sicher auf, Sie können es NICHT mehr ändern, wenn Sie es vergessen haben oder verlieren. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren. + token info + + + Please wait for token activation to complete. + Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist. + token info + + + Please wait for token to be registered. + Bitte warten Sie auf die Registrierung des Tokens. + token info + Polish interface Polnische Bedienoberfläche No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig @@ -4946,16 +5858,16 @@ Fehler: %@ Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren. No comment provided by engineer. - - Preset server - Voreingestellter Server - No comment provided by engineer. - Preset server address Voreingestellte Serveradresse No comment provided by engineer. + + Preset servers + Voreingestellte Server + No comment provided by engineer. + Preview Vorschau @@ -4971,16 +5883,36 @@ Fehler: %@ Datenschutz & Sicherheit No comment provided by engineer. + + Privacy for your customers. + Schutz der Privatsphäre Ihrer Kunden. + No comment provided by engineer. + + + Privacy policy and conditions of use. + Datenschutz- und Nutzungsbedingungen. + No comment provided by engineer. + Privacy redefined Datenschutz neu definiert 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. + No comment provided by engineer. + Private filenames Neutrale Dateinamen No comment provided by engineer. + + Private media file names. + Medien mit anonymisierten Dateinamen. + No comment provided by engineer. + Private message routing Privates Nachrichten-Routing @@ -5021,16 +5953,6 @@ Fehler: %@ Profil-Bilder No comment provided by engineer. - - Profile name - Profilname - No comment provided by engineer. - - - Profile name: - Profilname: - No comment provided by engineer. - Profile password Passwort für Profil @@ -5044,7 +5966,7 @@ Fehler: %@ Profile update will be sent to your contacts. Profil-Aktualisierung wird an Ihre Kontakte gesendet. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5988,11 @@ Fehler: %@ Reaktionen auf Nachrichten nicht erlauben. No comment provided by engineer. + + Prohibit reporting messages to moderators. + Melden von Nachrichten an Moderatoren nicht erlauben. + No comment provided by engineer. + Prohibit sending SimpleX links. Das Senden von SimpleX-Links nicht erlauben. @@ -5133,6 +6060,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Proxy-Server No comment provided by engineer. + + Proxy requires password + Der Proxy benötigt ein Passwort + No comment provided by engineer. + Push notifications Push-Benachrichtigungen @@ -5155,7 +6087,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reachable chat toolbar - Erreichbare Chat-Symbolleiste + Chat-Symbolleiste unten No comment provided by engineer. @@ -5173,26 +6105,21 @@ 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/app-settings.html#your-simplex-contact-address). - Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen. - 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). 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. - Erfahren Sie in unserem GitHub-Repository mehr dazu. - 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. @@ -5250,7 +6177,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Receiving file will be stopped. - Der Empfang der Datei wird beendet. + Das Herunterladen der Datei wird beendet. No comment provided by engineer. @@ -5323,11 +6250,26 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reduzierter Batterieverbrauch No comment provided by engineer. + + Register + Registrieren + No comment provided by engineer. + + + Register notification token? + Benachrichtigungs-Token registrieren? + token info + + + Registered + Registriert + token status text + Reject Ablehnen reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6296,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Entfernen No comment provided by engineer. + + Remove archive? + Archiv entfernen? + No comment provided by engineer. + Remove image Bild entfernen @@ -5419,6 +6366,56 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Antwort chat item action + + Report + Melden + chat item action + + + Report content: only group moderators will see it. + Inhalt melden: Nur Gruppenmoderatoren werden es sehen. + report reason + + + Report member profile: only group moderators will see it. + Mitgliederprofil melden: Nur Gruppenmoderatoren werden es sehen. + report reason + + + Report other: only group moderators will see it. + Anderes melden: Nur Gruppenmoderatoren werden es sehen. + report reason + + + Report reason? + Grund der Meldung? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Spam melden: Nur Gruppenmoderatoren werden es sehen. + report reason + + + Report violation: only group moderators will see it. + Verstoß melden: Nur Gruppenmoderatoren werden es sehen. + report reason + + + Report: %@ + Meldung: %@ + report in notification + + + Reporting messages to moderators is prohibited. + Melden von Nachrichten an Moderatoren ist nicht erlaubt. + No comment provided by engineer. + + + Reports + Meldungen + No comment provided by engineer. + Required Erforderlich @@ -5504,6 +6501,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Aufdecken chat item action + + Review conditions + Nutzungsbedingungen einsehen + No comment provided by engineer. + Revoke Widerrufen @@ -5534,9 +6536,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SMP-Server No comment provided by engineer. + + SOCKS proxy + SOCKS-Proxy + No comment provided by engineer. + Safely receive files - Dateien sicher empfangen + Dateien sicher herunterladen No comment provided by engineer. @@ -5547,17 +6554,18 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save Speichern - chat item action + alert button +chat item action Save (and notify contacts) Speichern (und Kontakte benachrichtigen) - No comment provided by engineer. + alert button Save and notify contact Speichern und Kontakt benachrichtigen - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6582,16 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Gruppen-Profil sichern und aktualisieren No comment provided by engineer. - - Save archive - Archiv speichern - No comment provided by engineer. - - - Save auto-accept settings - Einstellungen von "Automatisch akzeptieren" speichern - No comment provided by engineer. - Save group profile Gruppenprofil speichern No comment provided by engineer. + + Save list + Liste speichern + No comment provided by engineer. + Save passphrase and open chat Passwort speichern und Chat öffnen @@ -5602,7 +6605,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save preferences? Präferenzen speichern? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6620,18 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save servers? Alle Server speichern? - No comment provided by engineer. - - - Save settings? - Einstellungen speichern? - No comment provided by engineer. + alert title Save welcome message? Begrüßungsmeldung speichern? No comment provided by engineer. + + Save your profile? + Ihr Profil speichern? + alert title + Saved Abgespeichert @@ -5649,6 +6652,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Gespeicherte Nachricht message info title + + Saving %lld messages + Es wird/werden %lld Nachricht(en) gesichert + No comment provided by engineer. + Scale Skalieren @@ -5696,7 +6704,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Search or paste SimpleX link - Suchen oder fügen Sie den SimpleX-Link ein + Suchen oder SimpleX-Link einfügen No comment provided by engineer. @@ -5729,6 +6737,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Auswählen chat item action + + Select chat profile + Chat-Profil auswählen + No comment provided by engineer. + Selected %lld %lld ausgewählt @@ -5819,9 +6832,9 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Benachrichtigungen senden No comment provided by engineer. - - Send notifications: - Benachrichtigungen senden: + + Send private reports + Private Meldungen senden No comment provided by engineer. @@ -5847,7 +6860,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sender cancelled file transfer. Der Absender hat die Dateiübertragung abgebrochen. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6957,16 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Über einen Proxy gesendet No comment provided by engineer. + + Server + Server + No comment provided by engineer. + + + Server added to operator %@. + Der Server wurde dem Betreiber %@ hinzugefügt. + alert message + Server address Server-Adresse @@ -5959,6 +6982,21 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel: %@. No comment provided by engineer. + + Server operator changed. + Der Server-Betreiber wurde geändert. + alert title + + + Server operators + Server-Betreiber + No comment provided by engineer. + + + Server protocol changed. + Das Server-Protokoll wurde geändert. + alert title + Server requires authorization to create queues, check password Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort @@ -6014,6 +7052,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Einen Tag festlegen No comment provided by engineer. + + Set chat name… + Chat-Name festlegen… + No comment provided by engineer. + Set contact name… Kontaktname festlegen… @@ -6034,6 +7077,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Anstelle der System-Authentifizierung festlegen. No comment provided by engineer. + + Set message expiration in chats. + Verfallsdatum von Nachrichten in Chats festlegen. + No comment provided by engineer. + Set passcode Zugangscode einstellen @@ -6064,6 +7112,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Einstellungen No comment provided by engineer. + + Settings were changed. + Die Einstellungen wurden geändert. + alert message + Shape profile images Form der Profil-Bilder @@ -6072,22 +7125,38 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share Teilen - chat item action + alert action +chat item action Share 1-time link Einmal-Link teilen No comment provided by engineer. + + Share 1-time link with a friend + Den Einmal-Einladungslink mit einem Freund teilen + No comment provided by engineer. + + + Share SimpleX address on social media. + Die SimpleX-Adresse auf sozialen Medien teilen. + No comment provided by engineer. + Share address Adresse teilen No comment provided by engineer. + + Share address publicly + Die Adresse öffentlich teilen + No comment provided by engineer. + Share address with contacts? Die Adresse mit Kontakten teilen? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7168,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Link teilen No comment provided by engineer. + + Share profile + Profil teilen + No comment provided by engineer. + Share this 1-time invite link Teilen Sie diesen Einmal-Einladungslink @@ -6114,6 +7188,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Mit Kontakten teilen No comment provided by engineer. + + Short link + Verkürzter Link + No comment provided by engineer. + Show QR code QR-Code anzeigen @@ -6169,6 +7248,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Adresse 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. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft. @@ -6199,6 +7283,21 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Adresse No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + Die SimpleX-Adresse und Einmal-Links können sicher über beliebige Messenger geteilt werden. + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX-Adresse oder Einmal-Link? + No comment provided by engineer. + + + SimpleX channel link + SimpleX-Kanal-Link + simplex link type + SimpleX contact address SimpleX-Kontaktadressen-Link @@ -6219,8 +7318,8 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Links chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. In dieser Gruppe sind SimpleX-Links nicht erlaubt. No comment provided by engineer. @@ -6234,6 +7333,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Einmal-Einladung simplex link type + + SimpleX protocols reviewed by Trail of Bits. + Die SimpleX-Protokolle wurden von Trail of Bits überprüft. + No comment provided by engineer. + Simplified incognito mode Vereinfachter Inkognito-Modus @@ -6264,6 +7368,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Weich blur media + + Some app settings were not migrated. + Einige App-Einstellungen wurden nicht migriert. + No comment provided by engineer. + Some file(s) were not exported: Einzelne Datei(en) wurde(n) nicht exportiert: @@ -6279,11 +7388,24 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Während des Imports traten ein paar nicht schwerwiegende Fehler auf: No comment provided by engineer. + + Some servers failed the test: +%@ + Einige Server haben den Test nicht bestanden: +%@ + alert message + Somebody Jemand notification title + + Spam + Spam + blocking reason +report reason + Square, circle, or anything in between. Quadratisch, kreisförmig oder irgendetwas dazwischen. @@ -6329,11 +7451,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Chat beenden No comment provided by engineer. - - Stop chat to enable database actions - Chat beenden, um Datenbankaktionen zu erlauben - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Beenden Sie den Chat, um die Chat-Datenbank zu exportieren, zu importieren oder zu löschen. Solange der Chat angehalten ist, können Sie keine Nachrichten empfangen oder senden. @@ -6346,34 +7463,39 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Stop file - Datei beenden + Herunterladen beenden cancel file action Stop receiving file? - Den Empfang der Datei beenden? + Das Herunterladen der Datei beenden? No comment provided by engineer. Stop sending file? - Das Senden der Datei beenden? + Das Hochladen der Datei beenden? No comment provided by engineer. Stop sharing Teilen beenden - No comment provided by engineer. + alert action Stop sharing address? Das Teilen der Adresse beenden? - No comment provided by engineer. + alert title Stopping chat Chat wird beendet No comment provided by engineer. + + Storage + Ablage + No comment provided by engineer. + Strong Hart @@ -6404,6 +7526,16 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Unterstützung von SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Während des Anrufs zwischen Audio und Video wechseln + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Das Chat-Profil für Einmal-Einladungen wechseln + No comment provided by engineer. + System System @@ -6424,6 +7556,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Timeout der TCP-Verbindung No comment provided by engineer. + + TCP port for messaging + TCP-Port für Nachrichtenübermittlung + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7576,21 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Sprechblasen-Format + No comment provided by engineer. + Take picture Machen Sie ein Foto 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 button Schaltfläche antippen @@ -6482,13 +7629,18 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Temporary file error Temporärer Datei-Fehler - No comment provided by engineer. + file error alert title Test failed at step %@. Der Test ist beim Schritt %@ fehlgeschlagen. server test failure + + Test notifications + Benachrichtigungen testen + No comment provided by engineer. + Test server Teste Server @@ -6502,7 +7654,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Tests failed! Tests sind fehlgeschlagen! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7671,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Dank der Nutzer - Tragen Sie per Weblate bei! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Die erste Plattform ohne Benutzerkennungen – Privat per Design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7683,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen. 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 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). @@ -6551,6 +7703,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, your contact may be offline. + Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Die von Ihnen akzeptierte Verbindung wird abgebrochen! @@ -6571,6 +7728,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 + No comment provided by engineer. + The hash of the previous message is different. Der Hash der vorherigen Nachricht unterscheidet sich. @@ -6588,7 +7750,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The messages will be deleted for all members. - Die Nachrichten werden für alle Mitglieder gelöscht werden. + Die Nachrichten werden für alle Gruppenmitglieder gelöscht. No comment provided by engineer. @@ -6596,19 +7758,19 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden. No comment provided by engineer. - - The next generation of private messaging - Die nächste Generation von privatem Messaging - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden. No comment provided by engineer. - - The profile is only shared with your contacts. - Das Profil wird nur mit Ihren Kontakten geteilt. + + The same conditions will apply to operator **%@**. + Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + Der zweite voreingestellte Netzwerk-Betreiber in der App! No comment provided by engineer. @@ -6623,7 +7785,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The servers for new connections of your current chat profile **%@**. - Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**. + Nachrichten-Server für neue Verbindungen über Ihr aktuelles Chat-Profil **%@**. + No comment provided by engineer. + + + The servers for new files of your current chat profile **%@**. + Medien- und Datei-Server für neue Daten über Ihr aktuelles Chat-Profil **%@**. No comment provided by engineer. @@ -6631,11 +7798,21 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der von Ihnen eingefügte Text ist kein SimpleX-Link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt. + No comment provided by engineer. + Themes Design No comment provided by engineer. + + These conditions will also apply for: **%@**. + Diese Nutzungsbedingungen gelten auch für: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Diese Einstellungen betreffen Ihr aktuelles Profil **%@**. @@ -6648,17 +7825,22 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. + Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! 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. - Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. + Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. Diese Aktion kann nicht rückgängig gemacht werden! No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Die älteren als die ausgewählten gesendeten und empfangenen Nachrichten in diesem Chat werden gelöscht. Diese Aktion kann nicht rückgängig gemacht werden! + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Diese Aktion kann nicht rückgängig gemacht werden! Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. + Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. Diese Aktion kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -6701,11 +7883,21 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Das ist Ihr eigener Einmal-Link! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App. No comment provided by engineer. + + This message was deleted or not received yet. + Diese Nachricht wurde gelöscht oder bisher noch nicht empfangen. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**. @@ -6736,9 +7928,9 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Um eine Verbindung mit einem neuen Kontakt zu erstellen No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind. + + 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. No comment provided by engineer. @@ -6758,6 +7950,26 @@ You will be prompted to complete authentication before this feature is enabled.< Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funktion aktiviert wird. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind. + No comment provided by engineer. + + + To receive + Für den Empfang + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Bitte erteilen Sie für Video-Aufnahmen die Genehmigung die Kamera zu nutzen. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Bitte erlauben Sie die Nutzung des Mikrofons, um Sprachnachrichten aufnehmen zu können. @@ -6768,11 +7980,21 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Ihre Chat-Profile** ein, um Ihr verborgenes Profil zu sehen. No comment provided by engineer. + + To send + Für das Senden + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren. + 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. @@ -6788,6 +8010,11 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Inkognito beim Verbinden einschalten. No comment provided by engineer. + + Token status: %@. + Token-Status: %@. + token status + Toolbar opacity Deckkraft der Symbolleiste @@ -6863,6 +8090,11 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Mitglied freigeben? No comment provided by engineer. + + Undelivered messages + Nicht ausgelieferte Nachrichten + No comment provided by engineer. + Unexpected migration state Unerwarteter Migrationsstatus @@ -6911,7 +8143,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Unknown servers! Unbekannte Server! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +8180,18 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Unmute Stummschaltung aufheben - swipe action + notification label action Unread Ungelesen swipe action + + 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. @@ -6980,6 +8217,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Einstellungen aktualisieren? No comment provided by engineer. + + Updated conditions + Aktualisierte Nutzungsbedingungen + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden. @@ -7020,16 +8262,36 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Archiv wird hochgeladen No comment provided by engineer. + + Use %@ + Verwende %@ + No comment provided by engineer. + Use .onion hosts Verwende .onion-Hosts No comment provided by engineer. + + Use SOCKS proxy + SOCKS-Proxy nutzen + No comment provided by engineer. + Use SimpleX Chat servers? Verwenden Sie SimpleX-Chat-Server? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + Solange kein Port konfiguriert ist, wird TCP-Port %@ genutzt. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + TCP-Port 443 nur für voreingestellte Server verwenden. + No comment provided by engineer. + Use chat Verwenden Sie Chat @@ -7040,6 +8302,16 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Aktuelles Profil nutzen No comment provided by engineer. + + Use for files + Für Dateien verwenden + No comment provided by engineer. + + + Use for messages + Für Nachrichten verwenden + No comment provided by engineer. + Use for new connections Für neue Verbindungen nutzen @@ -7080,6 +8352,16 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Server nutzen No comment provided by engineer. + + Use servers + Verwende Server + No comment provided by engineer. + + + Use short links (BETA) + Kurze Links verwenden (BETA) + No comment provided by engineer. + Use the app while in the call. Die App kann während eines Anrufs genutzt werden. @@ -7087,12 +8369,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use the app with one hand. - Die App mit einer Hand nutzen. + Die App mit einer Hand bedienen. No comment provided by engineer. - - User profile - Benutzerprofil + + Use web port + Web-Port nutzen No comment provided by engineer. @@ -7100,6 +8382,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Benutzer-Auswahl No comment provided by engineer. + + Username + Benutzername + No comment provided by engineer. + Using SimpleX Chat servers. Verwendung von SimpleX-Chat-Servern. @@ -7157,12 +8444,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Video will be received when your contact completes uploading it. - Das Video wird empfangen, sobald Ihr Kontakt das Hochladen beendet hat. + Das Video wird heruntergeladen, sobald Ihr Kontakt das Hochladen beendet hat. No comment provided by engineer. Video will be received when your contact is online, please wait or check later! - Das Video wird empfangen, wenn Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später! + Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später! No comment provided by engineer. @@ -7170,11 +8457,21 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Videos und Dateien bis zu 1GB No comment provided by engineer. + + View conditions + Nutzungsbedingungen anschauen + No comment provided by engineer. + View security code Schauen Sie sich den Sicherheitscode an No comment provided by engineer. + + View updated conditions + Aktualisierte Nutzungsbedingungen anschauen + No comment provided by engineer. + Visible history Sichtbarer Nachrichtenverlauf @@ -7190,8 +8487,8 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s In diesem Chat sind Sprachnachrichten nicht erlaubt. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. In dieser Gruppe sind Sprachnachrichten nicht erlaubt. No comment provided by engineer. @@ -7285,9 +8582,9 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Bei der Verbindung über Audio- und Video-Anrufe. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Wenn mehrere Netzwerk-Betreiber aktiviert sind, hat keiner von ihnen Metadaten, um zu erfahren, wer mit wem kommuniziert. No comment provided by engineer. @@ -7333,7 +8630,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8657,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s XFTP-Server No comment provided by engineer. - - You - Profil - No comment provided by engineer. - You **must not** use the same database on two devices. Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen. @@ -7390,6 +8682,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Sie sind bereits mit %@ verbunden. No comment provided by engineer. + + You are already connected with %@. + Sie sind bereits mit %@ verbunden. + No comment provided by engineer. + You are already connecting to %@. Sie sind bereits mit %@ verbunden. @@ -7452,6 +8749,11 @@ Verbindungsanfrage wiederholen? Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden. No comment provided by engineer. + + You can configure servers via settings. + Sie können die Server über die Einstellungen konfigurieren. + No comment provided by engineer. + You can create it later Sie können dies später erstellen @@ -7492,6 +8794,11 @@ Verbindungsanfrage wiederholen? Sie können aus den archivierten Kontakten heraus Nachrichten an %@ versenden. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + Sie können einen Verbindungsnamen festlegen, um sich zu merken, mit wem der Link geteilt wurde. + 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. @@ -7507,11 +8814,6 @@ Verbindungsanfrage wiederholen? Sie können diese Adresse mit Ihren Kontakten teilen, um sie mit **%@** verbinden zu lassen. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten @@ -7519,7 +8821,7 @@ Verbindungsanfrage wiederholen? You can still view conversation with %@ in the list of chats. - Sie können in der Chatliste weiterhin die Unterhaltung mit %@ einsehen. + Sie können in der Chat-Liste weiterhin die Unterhaltung mit %@ einsehen. No comment provided by engineer. @@ -7535,23 +8837,23 @@ Verbindungsanfrage wiederholen? You can view invitation link again in connection details. Den Einladungslink können Sie in den Details der Verbindung nochmals sehen. - No comment provided by engineer. + alert message You can't send messages! Sie können keine Nachrichten versenden! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Sie können selbst festlegen, über welche Server Sie Ihre Nachrichten **empfangen** und an Ihre Kontakte **senden** wollen. - No comment provided by engineer. - You could not be verified; please try again. Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut. No comment provided by engineer. + + You decide who can connect. + Sie entscheiden, wer sich mit Ihnen verbinden kann. + No comment provided by engineer. + You have already requested connection via this address! Sie haben über diese Adresse bereits eine Verbindung beantragt! @@ -7619,6 +8921,11 @@ Verbindungsanfrage wiederholen? Sie haben eine Gruppeneinladung gesendet No comment provided by engineer. + + You should receive notifications. + Sie sollten Benachrichtigungen erhalten. + token info + You will be connected to group when the group host's device is online, please wait or check later! Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach! @@ -7654,6 +8961,11 @@ Verbindungsanfrage wiederholen? 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 chat. Chat history will be preserved. + Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf bleibt erhalten. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten. @@ -7674,31 +8986,16 @@ Verbindungsanfrage wiederholen? Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt No comment provided by engineer. - - Your %@ servers - Ihre %@-Server - No comment provided by engineer. - Your ICE servers Ihre ICE-Server No comment provided by engineer. - - Your SMP servers - Ihre SMP-Server - No comment provided by engineer. - Your SimpleX address Ihre SimpleX-Adresse No comment provided by engineer. - - Your XFTP servers - Ihre XFTP-Server - No comment provided by engineer. - Your calls Anrufe @@ -7714,11 +9011,21 @@ Verbindungsanfrage wiederholen? Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen. No comment provided by engineer. + + Your chat preferences + Ihre Chat-Präferenzen + alert title + Your chat profiles Ihre Chat-Profile No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@). @@ -7734,6 +9041,11 @@ Verbindungsanfrage wiederholen? Ihre Kontakte bleiben weiterhin verbunden. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Ihre Anmeldeinformationen können unverschlüsselt versendet werden. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT. @@ -7751,7 +9063,7 @@ Verbindungsanfrage wiederholen? Your privacy - Ihre Privatsphäre + Privatsphäre No comment provided by engineer. @@ -7764,33 +9076,36 @@ Verbindungsanfrage wiederholen? Ihr Profil **%@** wird geteilt. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. -SimpleX-Server können Ihr Profil nicht einsehen. + + Your profile is stored on your device and only shared with your contacts. + Das Profil wird nur mit Ihren Kontakten geteilt. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen. No comment provided by engineer. + + 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. + alert message + Your random profile Ihr Zufallsprofil No comment provided by engineer. - - Your server - Ihr Server - No comment provided by engineer. - Your server address Ihre Serveradresse No comment provided by engineer. + + Your servers + Ihre Server + No comment provided by engineer. + Your settings Einstellungen @@ -7831,6 +9146,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. Anruf angenommen call status + + accepted invitation + Einladung angenommen + chat list item title + admin Admin @@ -7866,6 +9186,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. und %lld weitere Ereignisse No comment provided by engineer. + + archived report + Archivierte Meldung + No comment provided by engineer. + attempts Versuche @@ -7904,7 +9229,8 @@ SimpleX-Server können Ihr Profil nicht einsehen. blocked by admin wurde vom Administrator blockiert - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9345,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. connecting… Verbinde… - chat list item title + No comment provided by engineer. connection established @@ -8073,8 +9399,9 @@ SimpleX-Server können Ihr Profil nicht einsehen. default (%@) - Voreinstellung (%@) - pref value + Default (%@) + delete after time +pref value default (no) @@ -8201,14 +9528,9 @@ SimpleX-Server können Ihr Profil nicht einsehen. Fehler No comment provided by engineer. - - event happened - event happened - No comment provided by engineer. - expired - abgelaufen + Abgelaufen No comment provided by engineer. @@ -8376,20 +9698,20 @@ SimpleX-Server können Ihr Profil nicht einsehen. Von %@ moderiert marked deleted chat item preview text + + moderator + Moderator + member role + months Monate time unit - - mute - Stummschalten - No comment provided by engineer. - never nie - No comment provided by engineer. + delete after time new message @@ -8420,8 +9742,8 @@ SimpleX-Server können Ihr Profil nicht einsehen. off Aus enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8440,7 +9762,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. other - andere + Andere No comment provided by engineer. @@ -8463,6 +9785,16 @@ SimpleX-Server können Ihr Profil nicht einsehen. Peer-to-Peer No comment provided by engineer. + + pending + ausstehend + No comment provided by engineer. + + + pending approval + ausstehende Genehmigung + No comment provided by engineer. + quantum resistant e2e encryption Quantum-resistente E2E-Verschlüsselung @@ -8478,6 +9810,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. Bestätigung erhalten… No comment provided by engineer. + + rejected + abgelehnt + No comment provided by engineer. + rejected call Abgelehnter Anruf @@ -8508,6 +9845,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. hat Sie aus der Gruppe entfernt rcv group event chat item + + requested to connect + Zur Verbindung aufgefordert + chat list item title + saved abgespeichert @@ -8607,11 +9949,6 @@ Zuletzt empfangene Nachricht: %2$@ unbekannter Gruppenmitglieds-Status No comment provided by engineer. - - unmute - Stummschaltung aufheben - No comment provided by engineer. - unprotected Ungeschützt @@ -8776,7 +10113,7 @@ Zuletzt empfangene Nachricht: %2$@
- +
@@ -8806,14 +10143,14 @@ Zuletzt empfangene Nachricht: %2$@ SimpleX needs access to Photo Library for saving captured and received media - SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder empfangene Bilder zu speichern + SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder heruntergeladene Bilder zu speichern Privacy - Photo Library Additions Usage Description
- +
@@ -8833,9 +10170,41 @@ Zuletzt empfangene Nachricht: %2$@
+ +
+ +
+ + + %d new events + %d neue Ereignisse + notification body + + + From %d chat(s) + Von %d Chat(s) + notification body + + + From: %@ + Von: %@ + notification body + + + New events + Neue Ereignisse + notification + + + New messages + Neue Nachrichten + notification + + +
- +
@@ -8850,14 +10219,14 @@ Zuletzt empfangene Nachricht: %2$@ Copyright © 2024 SimpleX Chat. All rights reserved. - Copyright © 2024 SimpleX Chat. Alle Rechte vorbehalten. + Copyright © 2025 SimpleX Chat. Alle Rechte vorbehalten. Copyright (human-readable)
- +
@@ -8892,17 +10261,17 @@ Zuletzt empfangene Nachricht: %2$@ Currently maximum supported file size is %@. - Die maximale erlaubte Dateigröße beträgt aktuell %@. + Die maximal erlaubte Dateigröße beträgt aktuell %@. No comment provided by engineer. Database downgrade required - Datenbank-Herabstufung erforderlich + Datenbank-Herunterstufung ist erforderlich No comment provided by engineer. Database encrypted! - Datenbank verschlüsselt! + Datenbank ist verschlüsselt! No comment provided by engineer. @@ -8917,7 +10286,7 @@ Zuletzt empfangene Nachricht: %2$@ Database passphrase is required to open chat. - Ein Datenbank-Passwort ist erforderlich, um den Chat zu öffnen. + Um den Chat zu öffnen, ist ein Datenbank-Passwort ist erforderlich. No comment provided by engineer. @@ -8942,7 +10311,7 @@ Zuletzt empfangene Nachricht: %2$@ File error - Dateifehler + Datei-Fehler No comment provided by engineer. @@ -8977,12 +10346,12 @@ Zuletzt empfangene Nachricht: %2$@ Open the app to downgrade the database. - Öffne die App, um die Datenbank herabzustufen. + Öffnen Sie die App, um die Datenbank herunterzustufen. No comment provided by engineer. Open the app to upgrade the database. - Öffne die App, um die Datenbank zu aktualisieren. + Öffnen Sie die App, um die Datenbank zu aktualisieren. No comment provided by engineer. @@ -8992,7 +10361,7 @@ Zuletzt empfangene Nachricht: %2$@ Please create a profile in the SimpleX app - Bitte erstelle ein Profil in der SimpleX-App + Bitte erstellen Sie ein Profil in der SimpleX-App No comment provided by engineer. @@ -9042,7 +10411,7 @@ Zuletzt empfangene Nachricht: %2$@ You can allow sharing in Privacy & Security / SimpleX Lock settings. - Du kannst das Teilen in den Einstellungen zu Datenschutz & Sicherheit - SimpleX-Sperre erlauben. + Sie können das Teilen in den Einstellungen zu Datenschutz & Sicherheit / SimpleX-Sperre erlauben. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/de.xcloc/contents.json b/apps/ios/SimpleX Localizations/de.xcloc/contents.json index 18b517d802..e8d71cf38c 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/de.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "de", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index b8432a33b6..fc1846942c 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -186,20 +186,16 @@ Available in v5.1 ) No comment provided by engineer.
- - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. No comment provided by engineer. @@ -210,8 +206,8 @@ Available in v5.1 **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. No comment provided by engineer. @@ -1128,8 +1124,8 @@ Available in v5.1 Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1144,8 +1140,8 @@ Available in v5.1 Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1580,16 +1576,16 @@ Available in v5.1 Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1708,8 +1704,8 @@ Available in v5.1 Immediately No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1821,8 +1817,8 @@ Available in v5.1 Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2016,8 +2012,8 @@ Available in v5.1 Migration is completed No comment provided by engineer. - - Migrations: %@ + + Migrations: No comment provided by engineer. @@ -2174,8 +2170,8 @@ Available in v5.1 Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2234,8 +2230,8 @@ Available in v5.1 Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2290,8 +2286,8 @@ Available in v5.1 Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2994,8 +2990,8 @@ Available in v5.1 Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3039,16 +3035,16 @@ 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. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -3111,8 +3107,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3337,8 +3333,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -3478,10 +3474,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -4200,7 +4192,7 @@ SimpleX servers cannot see your profile. ## In reply to - ## Ως απαντηση σε + ## Ως απάντηση σε copied message info 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 c217793f03..fd71e0dee6 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (can be copied) @@ -127,6 +100,16 @@ %@ is verified No comment provided by engineer. + + %@ server + %@ server + No comment provided by engineer. + + + %@ servers + %@ servers + No comment provided by engineer. + %@ uploaded %@ uploaded @@ -137,6 +120,11 @@ %@ wants to connect! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ and %lld members @@ -157,11 +145,36 @@ %d days time interval + + %d file(s) are still being downloaded. + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d hours time interval + + %d messages not forwarded + %d messages not forwarded + alert title + %d min %d min @@ -177,6 +190,11 @@ %d sec time interval + + %d seconds(s) + %d seconds(s) + delete after time + %d skipped message(s) %d skipped message(s) @@ -247,11 +265,6 @@ %lld new interface languages No comment provided by engineer. - - %lld second(s) - %lld second(s) - No comment provided by engineer. - %lld seconds %lld seconds @@ -302,11 +315,6 @@ %u messages skipped. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (new) @@ -317,19 +325,9 @@ (this device v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Add new contact**: to create your one-time QR Code or link for your contact. + + **Create 1-time link**: to create and share a new invitation link. + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. @@ -337,14 +335,14 @@ **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. + **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. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. + **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. No comment provided by engineer. @@ -357,9 +355,14 @@ **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. + **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. + No comment provided by engineer. + + + **Scan / Paste link**: to connect via a link you received. + **Scan / Paste link**: to connect via a link you received. No comment provided by engineer. @@ -387,11 +390,6 @@ \*bold* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - editing history. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -446,7 +439,8 @@ 1 day 1 day - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 month - time interval + delete after time +time interval 1 week 1 week - time interval + delete after time +time interval + + + 1 year + 1 year + delete after time + + + 1-time link + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 seconds No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,19 +543,14 @@ Abort changing address? No comment provided by engineer. - - About SimpleX - About SimpleX - No comment provided by engineer. - About SimpleX Chat About SimpleX Chat No comment provided by engineer. - - About SimpleX address - About SimpleX address + + About operators + About operators No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Accept accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Accept conditions + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Accept incognito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Accepted conditions + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Acknowledgement errors No comment provided by engineer. + + Active + Active + token status text + Active connections Active connections @@ -600,14 +616,14 @@ Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. - - Add contact - Add contact + + Add friends + Add friends No comment provided by engineer. - - Add preset servers - Add preset servers + + Add list + Add list No comment provided by engineer. @@ -625,16 +641,41 @@ Add servers by scanning QR codes. No comment provided by engineer. + + Add team members + Add team members + No comment provided by engineer. + Add to another device Add to another device No comment provided by engineer. + + Add to list + Add to list + No comment provided by engineer. + Add welcome message Add welcome message No comment provided by engineer. + + Add your team members to the conversations. + Add your team members to the conversations. + No comment provided by engineer. + + + Added media & file servers + Added media & file servers + No comment provided by engineer. + + + Added message servers + Added message servers + No comment provided by engineer. + Additional accent Additional accent @@ -660,6 +701,16 @@ Address change will be aborted. Old receiving address will be used. No comment provided by engineer. + + Address or 1-time link? + Address or 1-time link? + No comment provided by engineer. + + + Address settings + Address settings + No comment provided by engineer. + Admins can block a member for all. Admins can block a member for all. @@ -680,6 +731,11 @@ Advanced settings No comment provided by engineer. + + All + All + No comment provided by engineer. + All app data is deleted. All app data is deleted. @@ -690,14 +746,19 @@ All chats and messages will be deleted - this cannot be undone! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. All data is erased when it is entered. No comment provided by engineer. - - All data is private to your device. - All data is private to your device. + + All data is kept private on your device. + All data is kept private on your device. No comment provided by engineer. @@ -705,6 +766,11 @@ All group members will remain connected. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + 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! All messages will be deleted - this cannot be undone! @@ -723,6 +789,16 @@ All profiles All profiles + profile dropdown + + + All reports will be archived for you. + All reports will be archived for you. + No comment provided by engineer. + + + All servers + All servers No comment provided by engineer. @@ -800,6 +876,11 @@ Allow to irreversibly delete sent messages. (24 hours) No comment provided by engineer. + + Allow to report messsages to moderators. + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. Allow to send SimpleX links. @@ -880,11 +961,21 @@ An empty chat profile with the provided name is created, and the app opens as usual. No comment provided by engineer. + + Another reason + Another reason + report reason + Answer call Answer call No comment provided by engineer. + + Anybody can host servers. + Anybody can host servers. + No comment provided by engineer. + App build: %@ App build: %@ @@ -900,6 +991,11 @@ App encrypts new local files (except videos). No comment provided by engineer. + + App group: + App group: + No comment provided by engineer. + App icon App icon @@ -915,6 +1011,11 @@ App passcode is replaced with self-destruct passcode. No comment provided by engineer. + + App session + App session + No comment provided by engineer. + App version App version @@ -940,6 +1041,21 @@ Apply to No comment provided by engineer. + + Archive + Archive + No comment provided by engineer. + + + Archive %lld reports? + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? + Archive all reports? + No comment provided by engineer. + Archive and upload Archive and upload @@ -950,6 +1066,21 @@ Archive contacts to chat later. No comment provided by engineer. + + Archive report + Archive report + No comment provided by engineer. + + + Archive report? + Archive report? + No comment provided by engineer. + + + Archive reports + Archive reports + swipe action + Archived contacts Archived contacts @@ -1020,6 +1151,11 @@ Auto-accept images No comment provided by engineer. + + Auto-accept settings + Auto-accept settings + alert title + Back Back @@ -1045,11 +1181,26 @@ Bad message hash No comment provided by engineer. + + Better calls + Better calls + No comment provided by engineer. + Better groups Better groups No comment provided by engineer. + + Better groups performance + Better groups performance + No comment provided by engineer. + + + Better message dates. + Better message dates. + No comment provided by engineer. + Better messages Better messages @@ -1060,6 +1211,26 @@ Better networking No comment provided by engineer. + + Better notifications + Better notifications + No comment provided by engineer. + + + Better privacy and security + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + Better security ✅ + No comment provided by engineer. + + + Better user experience + Better user experience + No comment provided by engineer. + Black Black @@ -1140,11 +1311,35 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Business address + No comment provided by engineer. + + + Business chats + Business chats + No comment provided by engineer. + + + Businesses + Businesses + 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). 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! @@ -1193,7 +1388,8 @@ Cancel Cancel - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1409,7 @@ Cannot receive file Cannot receive file - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1426,16 @@ Change No comment provided by engineer. + + Change automatic message deletion? + Change automatic message deletion? + alert title + + + Change chat profiles + Change chat profiles + authentication reason + Change database passphrase? Change database passphrase? @@ -1274,11 +1480,21 @@ Change self-destruct passcode Change self-destruct passcode authentication reason - set passcode view +set passcode view - - Chat archive - Chat archive + + Chat + Chat + No comment provided by engineer. + + + Chat already exists + Chat already exists + No comment provided by engineer. + + + Chat already exists! + Chat already exists! No comment provided by engineer. @@ -1341,20 +1557,50 @@ Chat preferences No comment provided by engineer. + + Chat preferences were changed. + Chat preferences were changed. + alert message + + + Chat profile + Chat profile + No comment provided by engineer. + Chat theme 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! + No comment provided by engineer. + Chats Chats No comment provided by engineer. + + Check messages every 20 min. + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Check server address and try again. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1652,16 @@ Clear conversation? No comment provided by engineer. + + Clear group? + Clear group? + No comment provided by engineer. + + + Clear or delete group? + Clear or delete group? + No comment provided by engineer. + Clear private notes? Clear private notes? @@ -1426,6 +1682,11 @@ Color mode No comment provided by engineer. + + Community guidelines violation + Community guidelines violation + report reason + Compare file Compare file @@ -1441,14 +1702,49 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Conditions are already accepted for these operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Configure ICE servers No comment provided by engineer. - - Configured %@ servers - Configured %@ servers + + Configure server operators + Configure server operators No comment provided by engineer. @@ -1501,6 +1797,11 @@ Confirm upload No comment provided by engineer. + + Confirmed + Confirmed + token status text + Connect Connect @@ -1620,6 +1921,11 @@ This is your own one-time link! Connection and servers status. No comment provided by engineer. + + Connection blocked + Connection blocked + No comment provided by engineer. + Connection error Connection error @@ -1630,6 +1936,18 @@ This is your own one-time link! Connection error (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + Connection not ready. + No comment provided by engineer. + Connection notifications Connection notifications @@ -1640,6 +1958,16 @@ This is your own one-time link! Connection request sent! No comment provided by engineer. + + Connection requires encryption renegotiation. + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + Connection security + No comment provided by engineer. + Connection terminated Connection terminated @@ -1715,6 +2043,11 @@ This is your own one-time link! Contacts can mark messages for deletion; you will be able to view them. No comment provided by engineer. + + Content violates conditions of use + Content violates conditions of use + blocking reason + Continue Continue @@ -1740,6 +2073,11 @@ This is your own one-time link! Core version: v%@ No comment provided by engineer. + + Corner + Corner + No comment provided by engineer. + Correct name to %@? Correct name to %@? @@ -1750,6 +2088,11 @@ This is your own one-time link! Create No comment provided by engineer. + + Create 1-time link + Create 1-time link + No comment provided by engineer. + Create SimpleX address Create SimpleX address @@ -1760,11 +2103,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - Create an address to let people connect with you. - No comment provided by engineer. - Create file Create file @@ -1785,6 +2123,11 @@ This is your own one-time link! Create link No comment provided by engineer. + + Create list + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2168,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - Created on %@ - No comment provided by engineer. - Creating archive link Creating archive link @@ -1845,6 +2183,11 @@ This is your own one-time link! Current Passcode No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Current passphrase… @@ -1865,6 +2208,11 @@ This is your own one-time link! Custom time No comment provided by engineer. + + Customizable message shape. + Customizable message shape. + No comment provided by engineer. + Customize theme Customize theme @@ -1996,8 +2344,8 @@ This is your own one-time link! Delete Delete - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2382,14 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - Delete archive + + Delete chat + Delete chat No comment provided by engineer. - - Delete chat archive? - Delete chat archive? + + Delete chat messages from your device. + Delete chat messages from your device. No comment provided by engineer. @@ -2054,6 +2402,11 @@ This is your own one-time link! Delete chat profile? No comment provided by engineer. + + Delete chat? + Delete chat? + No comment provided by engineer. + Delete connection Delete connection @@ -2129,6 +2482,11 @@ This is your own one-time link! Delete link? No comment provided by engineer. + + Delete list? + Delete list? + alert title + Delete member message? Delete member message? @@ -2142,7 +2500,7 @@ This is your own one-time link! Delete messages Delete messages - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2517,11 @@ This is your own one-time link! Delete old database? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Delete pending connection? @@ -2174,6 +2537,11 @@ This is your own one-time link! Delete queue server test step + + Delete report + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. Delete up to 20 messages at once. @@ -2209,6 +2577,11 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Delivery @@ -2309,9 +2682,14 @@ This is your own one-time link! Direct messages chat feature - - Direct messages between members are prohibited in this group. - Direct messages between members are prohibited in this group. + + 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. @@ -2324,6 +2702,16 @@ This is your own one-time link! Disable SimpleX Lock authentication reason + + Disable automatic message deletion? + Disable automatic message deletion? + alert title + + + Disable delete messages + Disable delete messages + alert button + Disable for all Disable for all @@ -2349,9 +2737,9 @@ This is your own one-time link! Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. + Disappearing messages are prohibited. No comment provided by engineer. @@ -2409,6 +2797,16 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + Do not use credentials with proxy. + No comment provided by engineer. + + + Documents: + Documents: + No comment provided by engineer. + Don't create address Don't create address @@ -2419,11 +2817,21 @@ This is your own one-time link! Don't enable No comment provided by engineer. + + Don't miss important messages. + Don't miss important messages. + No comment provided by engineer. + Don't show again Don't show again No comment provided by engineer. + + Done + Done + No comment provided by engineer. + Downgrade and open chat Downgrade and open chat @@ -2432,7 +2840,8 @@ This is your own one-time link! Download Download - chat item action + alert button +chat item action Download errors @@ -2449,6 +2858,11 @@ This is your own one-time link! Download file server test step + + Download files + Download files + alert action + Downloaded Downloaded @@ -2479,6 +2893,11 @@ This is your own one-time link! Duration No comment provided by engineer. + + E2E encrypted notifications. + E2E encrypted notifications. + No comment provided by engineer. + Edit Edit @@ -2499,6 +2918,11 @@ This is your own one-time link! Enable (keep overrides) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Enable SimpleX Lock @@ -2512,7 +2936,7 @@ This is your own one-time link! Enable automatic message deletion? Enable automatic message deletion? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3063,11 @@ This is your own one-time link! Encryption re-negotiation failed. No comment provided by engineer. + + Encryption renegotiation in progress. + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Enter Passcode @@ -2704,26 +3133,36 @@ This is your own one-time link! Error aborting address change No comment provided by engineer. + + Error accepting conditions + Error accepting conditions + alert title + Error accepting contact request Error accepting contact request No comment provided by engineer. - - Error accessing database file - Error accessing database file - No comment provided by engineer. - Error adding member(s) Error adding member(s) No comment provided by engineer. + + Error adding server + Error adding server + alert title + Error changing address Error changing address No comment provided by engineer. + + Error changing connection profile + Error changing connection profile + No comment provided by engineer. + Error changing role Error changing role @@ -2734,6 +3173,16 @@ This is your own one-time link! Error changing setting No comment provided by engineer. + + Error changing to incognito! + Error changing to incognito! + No comment provided by engineer. + + + Error checking token status + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Error connecting to forwarding server %@. Please try later. @@ -2754,6 +3203,11 @@ This is your own one-time link! Error creating group link No comment provided by engineer. + + Error creating list + Error creating list + alert title + Error creating member contact Error creating member contact @@ -2769,6 +3223,11 @@ This is your own one-time link! Error creating profile! No comment provided by engineer. + + Error creating report + Error creating report + No comment provided by engineer. + Error decrypting file Error decrypting file @@ -2849,9 +3308,14 @@ This is your own one-time link! Error joining group No comment provided by engineer. - - Error loading %@ servers - Error loading %@ servers + + Error loading servers + Error loading servers + alert title + + + Error migrating settings + Error migrating settings No comment provided by engineer. @@ -2862,7 +3326,7 @@ This is your own one-time link! Error receiving file Error receiving file - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3338,36 @@ This is your own one-time link! Error reconnecting servers No comment provided by engineer. + + Error registering for notifications + Error registering for notifications + alert title + Error removing member Error removing member No comment provided by engineer. + + Error reordering lists + Error reordering lists + alert title + Error resetting statistics Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Error saving %@ servers - No comment provided by engineer. - Error saving ICE servers Error saving ICE servers No comment provided by engineer. + + Error saving chat list + Error saving chat list + alert title + Error saving group profile Error saving group profile @@ -2909,6 +3383,11 @@ This is your own one-time link! Error saving passphrase to keychain No comment provided by engineer. + + Error saving servers + Error saving servers + alert title + Error saving settings Error saving settings @@ -2954,16 +3433,26 @@ This is your own one-time link! Error stopping chat No comment provided by engineer. + + Error switching profile + Error switching profile + No comment provided by engineer. + Error switching profile! Error switching profile! - No comment provided by engineer. + alertTitle Error synchronizing connection Error synchronizing connection No comment provided by engineer. + + Error testing server connection + Error testing server connection + No comment provided by engineer. + Error updating group link Error updating group link @@ -2974,6 +3463,11 @@ This is your own one-time link! Error updating message No comment provided by engineer. + + Error updating server + Error updating server + alert title + Error updating settings Error updating settings @@ -3002,8 +3496,9 @@ This is your own one-time link! Error: %@ Error: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3515,11 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + Errors in servers configuration. + servers error + Even when disabled in the conversation. Even when disabled in the conversation. @@ -3035,6 +3535,11 @@ This is your own one-time link! Expand chat item action + + Expired + Expired + token status text + Export database Export database @@ -3075,20 +3580,49 @@ This is your own one-time link! Fast and no wait until the sender is online! No comment provided by engineer. + + Faster deletion of groups. + 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. + Faster sending messages. + No comment provided by engineer. + Favorite Favorite swipe action + + Favorites + Favorites + No comment provided by engineer. + File error File error - No comment provided by engineer. + file error alert title + + + File errors: +%@ + File errors: +%@ + alert message + + + File is blocked by server operator: +%@. + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,9 +3679,9 @@ This is your own one-time link! Files and media chat feature - - Files and media are prohibited in this group. - Files and media are prohibited in this group. + + Files and media are prohibited. + Files and media are prohibited. No comment provided by engineer. @@ -3215,21 +3749,71 @@ This is your own one-time link! Fix not supported by group member No comment provided by engineer. + + For all moderators + For all moderators + No comment provided by engineer. + + + For chat profile %@: + For chat profile %@: + servers error + For console For console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + For me + No comment provided by engineer. + + + For private routing + For private routing + No comment provided by engineer. + + + For social media + For social media + No comment provided by engineer. + Forward Forward chat item action + + Forward %d message(s)? + Forward %d message(s)? + alert title + Forward and save messages Forward and save messages No comment provided by engineer. + + Forward messages + Forward messages + alert action + + + Forward messages without files? + Forward messages without files? + alert message + + + Forward up to 20 messages at once. + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Forwarded @@ -3240,6 +3824,11 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Forwarding server %@ failed to connect to destination server %@. Please try later. @@ -3289,11 +3878,6 @@ Error: %2$@ Full name (optional) No comment provided by engineer. - - Full name: - Full name: - No comment provided by engineer. - Fully decentralized – visible only to members. Fully decentralized – visible only to members. @@ -3314,6 +3898,11 @@ Error: %2$@ GIFs and stickers No comment provided by engineer. + + Get notified when mentioned. + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! Good afternoon! @@ -3379,41 +3968,6 @@ Error: %2$@ Group links No comment provided by engineer. - - Group members can add message reactions. - Group members can add message reactions. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Group members can irreversibly delete sent messages. (24 hours) - No comment provided by engineer. - - - Group members can send SimpleX links. - Group members can send SimpleX links. - No comment provided by engineer. - - - Group members can send direct messages. - Group members can send direct messages. - No comment provided by engineer. - - - Group members can send disappearing messages. - Group members can send disappearing messages. - No comment provided by engineer. - - - Group members can send files and media. - Group members can send files and media. - No comment provided by engineer. - - - Group members can send voice messages. - Group members can send voice messages. - No comment provided by engineer. - Group message: Group message: @@ -3454,11 +4008,21 @@ Error: %2$@ Group will be deleted for you - this cannot be undone! No comment provided by engineer. + + Groups + Groups + No comment provided by engineer. + Help Help No comment provided by engineer. + + Help admins moderating their groups. + Help admins moderating their groups. + No comment provided by engineer. + Hidden Hidden @@ -3509,10 +4073,20 @@ Error: %2$@ How SimpleX works No comment provided by engineer. + + How it affects privacy + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + How it helps privacy + No comment provided by engineer. + How it works How it works - No comment provided by engineer. + alert button How to @@ -3539,6 +4113,11 @@ Error: %2$@ ICE servers (one per line) No comment provided by engineer. + + IP address + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. If you can't meet in person, show QR code in a video call, or share the link. @@ -3579,9 +4158,9 @@ Error: %2$@ Immediately No comment provided by engineer. - - Immune to spam and abuse - Immune to spam and abuse + + Immune to spam + Immune to spam No comment provided by engineer. @@ -3614,6 +4193,13 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Improved message delivery @@ -3644,6 +4230,16 @@ Error: %2$@ In-call sounds No comment provided by engineer. + + Inappropriate content + Inappropriate content + report reason + + + Inappropriate profile + Inappropriate profile + report reason + Incognito Incognito @@ -3714,6 +4310,11 @@ Error: %2$@ Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Instant + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4322,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Instantly - No comment provided by engineer. - Interface Interface @@ -3736,6 +4332,31 @@ Error: %2$@ Interface colors No comment provided by engineer. + + Invalid + Invalid + token status text + + + Invalid (bad token) + Invalid (bad token) + token status text + + + Invalid (expired) + Invalid (expired) + token status text + + + Invalid (unregistered) + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + Invalid (wrong topic) + token status text + Invalid QR code Invalid QR code @@ -3774,7 +4395,7 @@ Error: %2$@ Invalid server address! Invalid server address! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4417,11 @@ Error: %2$@ Invite members No comment provided by engineer. + + Invite to chat + Invite to chat + No comment provided by engineer. + Invite to group Invite to group @@ -3811,9 +4437,9 @@ Error: %2$@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -3902,7 +4528,7 @@ This is your link for group %@! Keep Keep - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4543,7 @@ This is your link for group %@! Keep unused invitation? Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4580,16 @@ This is your link for group %@! Leave swipe action + + Leave chat + Leave chat + No comment provided by engineer. + + + Leave chat? + Leave chat? + No comment provided by engineer. + Leave group Leave group @@ -3994,6 +4630,21 @@ This is your link for group %@! Linked desktops No comment provided by engineer. + + List + List + swipe action + + + List name and emoji should be different for all lists. + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + List name... + No comment provided by engineer. + Live message! Live message! @@ -4004,11 +4655,6 @@ This is your link for group %@! Live messages No comment provided by engineer. - - Local - Local - No comment provided by engineer. - Local name Local name @@ -4029,11 +4675,6 @@ This is your link for group %@! Lock mode No comment provided by engineer. - - Make a private connection - Make a private connection - No comment provided by engineer. - Make one message disappear Make one message disappear @@ -4044,21 +4685,11 @@ This is your link for group %@! Make profile private! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - No comment provided by engineer. - Mark deleted for everyone Mark deleted for everyone @@ -4104,6 +4735,16 @@ This is your link for group %@! Member inactive item status text + + Member reports + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Member role will be changed to "%@". All group members will be notified. @@ -4114,11 +4755,61 @@ This is your link for group %@! Member role will be changed to "%@". The member will receive a new invitation. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Member will be removed from group - this cannot be undone! No comment provided by engineer. + + Members can add message reactions. + Members can add message reactions. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Members can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Members can report messsages to moderators. + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Members can send SimpleX links. + 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. + Members can send disappearing messages. + No comment provided by engineer. + + + Members can send files and media. + Members can send files and media. + 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 Menus @@ -4169,9 +4860,9 @@ This is your link for group %@! Message reactions are prohibited in this chat. No comment provided by engineer. - - Message reactions are prohibited in this group. - Message reactions are prohibited in this group. + + Message reactions are prohibited. + Message reactions are prohibited. No comment provided by engineer. @@ -4184,6 +4875,11 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + Message shape + No comment provided by engineer. + Message source remains private. Message source remains private. @@ -4224,6 +4920,11 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this chat will never be deleted. + Messages in this chat will never be deleted. + alert message + Messages received Messages received @@ -4234,6 +4935,11 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. @@ -4299,9 +5005,9 @@ This is your link for group %@! Migration is completed No comment provided by engineer. - - Migrations: %@ - Migrations: %@ + + Migrations: + Migrations: No comment provided by engineer. @@ -4319,6 +5025,11 @@ This is your link for group %@! Moderated at: %@ copied message info + + More + More + swipe action + More improvements are coming soon! More improvements are coming soon! @@ -4329,6 +5040,11 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Most likely this connection is deleted. @@ -4342,7 +5058,12 @@ This is your link for group %@! Mute Mute - swipe action + notification label action + + + Mute all + Mute all + notification label action Muted when inactive! @@ -4364,6 +5085,11 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Network issues - message expired after many attempts to send it. @@ -4374,6 +5100,11 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + Network operator + No comment provided by engineer. + Network settings Network settings @@ -4384,11 +5115,26 @@ This is your link for group %@! Network status No comment provided by engineer. + + New + New + token status text + New Passcode New Passcode No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + New SOCKS credentials will be used every time you start the app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + New SOCKS credentials will be used for each server. + No comment provided by engineer. + New chat New chat @@ -4409,11 +5155,6 @@ This is your link for group %@! New contact: notification - - New database archive - New database archive - No comment provided by engineer. - New desktop app! New desktop app! @@ -4424,6 +5165,11 @@ This is your link for group %@! New display name No comment provided by engineer. + + New events + New events + notification + New in %@ New in %@ @@ -4449,6 +5195,11 @@ This is your link for group %@! New passphrase… No comment provided by engineer. + + New server + New server + No comment provided by engineer. + No No @@ -4459,6 +5210,21 @@ This is your link for group %@! No app password Authentication unavailable + + No chats + No chats + No comment provided by engineer. + + + No chats found + No chats found + No comment provided by engineer. + + + No chats in list %@ + No chats in list %@ + No comment provided by engineer. + No contacts selected No contacts selected @@ -4504,31 +5270,106 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + No media & file servers. + servers error + + + No message + No message + No comment provided by engineer. + + + No message servers. + No message servers. + servers error + No network connection No network connection No comment provided by engineer. + + No permission to record speech + No permission to record speech + No comment provided by engineer. + + + No permission to record video + No permission to record video + No comment provided by engineer. + No permission to record voice message No permission to record voice message No comment provided by engineer. + + No push server + No push server + No comment provided by engineer. + No received or sent files No received or sent files No comment provided by engineer. + + No servers for private message routing. + 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. + No servers to send files. + servers error + + + No token! + No token! + alert title + + + No unread chats + No unread chats + No comment provided by engineer. + + + No user identifiers. + No user identifiers. + No comment provided by engineer. + Not compatible! Not compatible! No comment provided by engineer. + + Notes + Notes + No comment provided by engineer. + Nothing selected Nothing selected No comment provided by engineer. + + Nothing to forward! + Nothing to forward! + alert title + Notifications Notifications @@ -4539,6 +5380,21 @@ This is your link for group %@! Notifications are disabled! No comment provided by engineer. + + Notifications error + Notifications error + alert title + + + Notifications privacy + Notifications privacy + No comment provided by engineer. + + + Notifications status + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5417,13 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database Old database No comment provided by engineer. - - Old database archive - Old database archive - No comment provided by engineer. - One-time invitation link One-time invitation link @@ -4597,9 +5448,14 @@ Requires compatible VPN. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -4622,6 +5478,16 @@ Requires compatible VPN. Only group owners can enable voice messages. No comment provided by engineer. + + Only sender and moderators see it + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Only you can add message reactions. @@ -4675,13 +5541,18 @@ Requires compatible VPN. Open Open - No comment provided by engineer. + alert action Open Settings Open Settings No comment provided by engineer. + + Open changes + Open changes + No comment provided by engineer. + Open chat Open chat @@ -4692,36 +5563,46 @@ Requires compatible VPN. Open chat console authentication reason + + Open conditions + Open conditions + No comment provided by engineer. + Open group Open group No comment provided by engineer. + + Open link? + Open link? + alert title + Open migration to another device Open migration to another device authentication reason - - Open server settings - Open server settings - No comment provided by engineer. - - - Open user profiles - Open user profiles - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Open-source protocol and code – anybody can run the servers. - No comment provided by engineer. - Opening app… Opening app… No comment provided by engineer. + + Operator + Operator + No comment provided by engineer. + + + Operator server + Operator server + alert title + + + Or import archive file + Or import archive file + No comment provided by engineer. + Or paste archive link Or paste archive link @@ -4742,15 +5623,27 @@ Requires compatible VPN. Or show this code No comment provided by engineer. + + Or to share privately + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + Organize chats into lists + No comment provided by engineer. + Other Other No comment provided by engineer. - - Other %@ servers - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + Other file errors: +%@ + alert message PING count @@ -4787,6 +5680,11 @@ Requires compatible VPN. Passcode set! No comment provided by engineer. + + Password + Password + No comment provided by engineer. + Password to show Password to show @@ -4822,14 +5720,9 @@ Requires compatible VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - People can connect to you only via the links you share. - No comment provided by engineer. - - - Periodically - Periodically + + Periodic + Periodic No comment provided by engineer. @@ -4931,11 +5824,31 @@ Error: %@ Please store passphrase securely, you will NOT be able to change it if you lose it. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + Please wait for token to be registered. + token info + Polish interface Polish interface No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Possibly, certificate fingerprint in server address is incorrect @@ -4946,16 +5859,16 @@ Error: %@ Preserve the last message draft, with attachments. No comment provided by engineer. - - Preset server - Preset server - No comment provided by engineer. - Preset server address Preset server address No comment provided by engineer. + + Preset servers + Preset servers + No comment provided by engineer. + Preview Preview @@ -4971,16 +5884,36 @@ Error: %@ Privacy & security No comment provided by engineer. + + Privacy for your customers. + Privacy for your customers. + No comment provided by engineer. + + + Privacy policy and conditions of use. + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Privacy redefined 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. + No comment provided by engineer. + Private filenames Private filenames No comment provided by engineer. + + Private media file names. + Private media file names. + No comment provided by engineer. + Private message routing Private message routing @@ -5021,16 +5954,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - Profile name - No comment provided by engineer. - - - Profile name: - Profile name: - No comment provided by engineer. - Profile password Profile password @@ -5044,7 +5967,7 @@ Error: %@ Profile update will be sent to your contacts. Profile update will be sent to your contacts. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5989,11 @@ Error: %@ Prohibit messages reactions. No comment provided by engineer. + + Prohibit reporting messages to moderators. + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. Prohibit sending SimpleX links. @@ -5133,6 +6061,11 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + Proxy requires password + No comment provided by engineer. + Push notifications Push notifications @@ -5173,26 +6106,21 @@ Enable in *Network & servers* settings. Read more No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Read more in our GitHub repository. - 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). @@ -5323,11 +6251,26 @@ Enable in *Network & servers* settings. Reduced battery usage No comment provided by engineer. + + Register + Register + No comment provided by engineer. + + + Register notification token? + Register notification token? + token info + + + Registered + Registered + token status text + Reject Reject reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6297,11 @@ Enable in *Network & servers* settings. Remove No comment provided by engineer. + + Remove archive? + Remove archive? + No comment provided by engineer. + Remove image Remove image @@ -5419,6 +6367,56 @@ Enable in *Network & servers* settings. Reply chat item action + + Report + Report + chat item action + + + Report content: only group moderators will see it. + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + Report other: only group moderators will see it. + report reason + + + Report reason? + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + Report violation: only group moderators will see it. + report reason + + + Report: %@ + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + Reports + No comment provided by engineer. + Required Required @@ -5504,6 +6502,11 @@ Enable in *Network & servers* settings. Reveal chat item action + + Review conditions + Review conditions + No comment provided by engineer. + Revoke Revoke @@ -5534,6 +6537,11 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + SOCKS proxy + No comment provided by engineer. + Safely receive files Safely receive files @@ -5547,17 +6555,18 @@ Enable in *Network & servers* settings. Save Save - chat item action + alert button +chat item action Save (and notify contacts) Save (and notify contacts) - No comment provided by engineer. + alert button Save and notify contact Save and notify contact - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6583,16 @@ Enable in *Network & servers* settings. Save and update group profile No comment provided by engineer. - - Save archive - Save archive - No comment provided by engineer. - - - Save auto-accept settings - Save auto-accept settings - No comment provided by engineer. - Save group profile Save group profile No comment provided by engineer. + + Save list + Save list + No comment provided by engineer. + Save passphrase and open chat Save passphrase and open chat @@ -5602,7 +6606,7 @@ Enable in *Network & servers* settings. Save preferences? Save preferences? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6621,18 @@ Enable in *Network & servers* settings. Save servers? Save servers? - No comment provided by engineer. - - - Save settings? - Save settings? - No comment provided by engineer. + alert title Save welcome message? Save welcome message? No comment provided by engineer. + + Save your profile? + Save your profile? + alert title + Saved Saved @@ -5649,6 +6653,11 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + Saving %lld messages + No comment provided by engineer. + Scale Scale @@ -5729,6 +6738,11 @@ Enable in *Network & servers* settings. Select chat item action + + Select chat profile + Select chat profile + No comment provided by engineer. + Selected %lld Selected %lld @@ -5819,9 +6833,9 @@ Enable in *Network & servers* settings. Send notifications No comment provided by engineer. - - Send notifications: - Send notifications: + + Send private reports + Send private reports No comment provided by engineer. @@ -5847,7 +6861,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Sender cancelled file transfer. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6958,16 @@ Enable in *Network & servers* settings. Sent via proxy No comment provided by engineer. + + Server + Server + No comment provided by engineer. + + + Server added to operator %@. + Server added to operator %@. + alert message + Server address Server address @@ -5959,6 +6983,21 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + Server operator changed. + alert title + + + Server operators + Server operators + No comment provided by engineer. + + + Server protocol changed. + Server protocol changed. + alert title + Server requires authorization to create queues, check password Server requires authorization to create queues, check password @@ -6014,6 +7053,11 @@ Enable in *Network & servers* settings. Set 1 day No comment provided by engineer. + + Set chat name… + Set chat name… + No comment provided by engineer. + Set contact name… Set contact name… @@ -6034,6 +7078,11 @@ Enable in *Network & servers* settings. Set it instead of system authentication. No comment provided by engineer. + + Set message expiration in chats. + Set message expiration in chats. + No comment provided by engineer. + Set passcode Set passcode @@ -6064,6 +7113,11 @@ Enable in *Network & servers* settings. Settings No comment provided by engineer. + + Settings were changed. + Settings were changed. + alert message + Shape profile images Shape profile images @@ -6072,22 +7126,38 @@ Enable in *Network & servers* settings. Share Share - chat item action + alert action +chat item action Share 1-time link Share 1-time link No comment provided by engineer. + + Share 1-time link with a friend + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + Share SimpleX address on social media. + No comment provided by engineer. + Share address Share address No comment provided by engineer. + + Share address publicly + Share address publicly + No comment provided by engineer. + Share address with contacts? Share address with contacts? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7169,11 @@ Enable in *Network & servers* settings. Share link No comment provided by engineer. + + Share profile + Share profile + No comment provided by engineer. + Share this 1-time invite link Share this 1-time invite link @@ -6114,6 +7189,11 @@ Enable in *Network & servers* settings. Share with contacts No comment provided by engineer. + + Short link + Short link + No comment provided by engineer. + Show QR code Show QR code @@ -6169,6 +7249,11 @@ Enable in *Network & servers* settings. SimpleX Address No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat security was audited by Trail of Bits. @@ -6199,6 +7284,21 @@ Enable in *Network & servers* settings. SimpleX address No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + SimpleX channel link + simplex link type + SimpleX contact address SimpleX contact address @@ -6219,9 +7319,9 @@ Enable in *Network & servers* settings. SimpleX links chat feature - - SimpleX links are prohibited in this group. - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. + SimpleX links are prohibited. No comment provided by engineer. @@ -6234,6 +7334,11 @@ Enable in *Network & servers* settings. SimpleX one-time invitation simplex link type + + SimpleX protocols reviewed by Trail of Bits. + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Simplified incognito mode @@ -6264,6 +7369,11 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Some file(s) were not exported: @@ -6279,11 +7389,24 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + Some servers failed the test: +%@ + alert message + Somebody Somebody notification title + + Spam + Spam + blocking reason +report reason + Square, circle, or anything in between. Square, circle, or anything in between. @@ -6329,11 +7452,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Stop chat to enable database actions - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. @@ -6362,18 +7480,23 @@ Enable in *Network & servers* settings. Stop sharing Stop sharing - No comment provided by engineer. + alert action Stop sharing address? Stop sharing address? - No comment provided by engineer. + alert title Stopping chat Stopping chat No comment provided by engineer. + + Storage + Storage + No comment provided by engineer. + Strong Strong @@ -6404,6 +7527,16 @@ Enable in *Network & servers* settings. Support SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System System @@ -6424,6 +7557,11 @@ Enable in *Network & servers* settings. TCP connection timeout No comment provided by engineer. + + TCP port for messaging + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7577,21 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Tail + No comment provided by engineer. + Take picture Take picture 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 button Tap button @@ -6482,13 +7630,18 @@ Enable in *Network & servers* settings. Temporary file error Temporary file error - No comment provided by engineer. + file error alert title Test failed at step %@. Test failed at step %@. server test failure + + Test notifications + Test notifications + No comment provided by engineer. + Test server Test server @@ -6502,7 +7655,7 @@ Enable in *Network & servers* settings. Tests failed! Tests failed! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7672,6 @@ Enable in *Network & servers* settings. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - The 1st platform without any user identifiers – private by design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7684,11 @@ It can happen because of some bug or when the connection is compromised.The app can notify you when you receive messages or contact requests - please open settings to enable. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). The app will ask to confirm downloads from unknown file servers (except .onion). @@ -6551,6 +7704,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, your contact may be offline. + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! The connection you accepted will be cancelled! @@ -6571,6 +7729,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 + No comment provided by engineer. + The hash of the previous message is different. The hash of the previous message is different. @@ -6596,19 +7759,19 @@ 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. - - The next generation of private messaging - The next generation of private messaging - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. - The profile is only shared with your contacts. + + The same conditions will apply to operator **%@**. + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + The second preset operator in the app! No comment provided by engineer. @@ -6626,16 +7789,31 @@ It can happen because of some bug or when the connection is compromised.The servers for new connections of your current chat profile **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. The text you pasted is not a SimpleX link. 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 Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. These settings are for your current profile **%@**. @@ -6656,6 +7834,11 @@ It can happen because of some bug or when the connection is compromised.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. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + 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. @@ -6701,11 +7884,21 @@ It can happen because of some bug or when the connection is compromised.This is your own one-time link! 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. + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. 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 **%@**. This setting applies to messages in your current chat profile **%@**. @@ -6736,9 +7929,9 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect against your link being replaced, you can compare contact security codes. + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6758,6 +7951,26 @@ You will be prompted to complete authentication before this feature is enabled.< You will be prompted to complete authentication before this feature is enabled. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + No comment provided by engineer. + + + To receive + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. To record voice message please grant permission to use Microphone. @@ -6768,11 +7981,21 @@ You will be prompted to complete authentication before this feature is enabled.< To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. No comment provided by engineer. + + To send + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. To support instant push notifications the chat database has to be migrated. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + 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. To verify end-to-end encryption with your contact compare (or scan) the code on your devices. @@ -6788,6 +8011,11 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle incognito when connecting. No comment provided by engineer. + + Token status: %@. + Token status: %@. + token status + Toolbar opacity Toolbar opacity @@ -6863,6 +8091,11 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. + + Undelivered messages + Undelivered messages + No comment provided by engineer. + Unexpected migration state Unexpected migration state @@ -6911,7 +8144,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +8181,18 @@ To connect, please ask your contact to create another connection link and check Unmute Unmute - swipe action + notification label action Unread Unread swipe action + + Unsupported connection link + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Up to 100 last messages are sent to new members. @@ -6980,6 +8218,11 @@ To connect, please ask your contact to create another connection link and check Update settings? No comment provided by engineer. + + Updated conditions + Updated conditions + 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. @@ -7020,16 +8263,36 @@ To connect, please ask your contact to create another connection link and check Uploading archive No comment provided by engineer. + + Use %@ + Use %@ + No comment provided by engineer. + Use .onion hosts Use .onion hosts No comment provided by engineer. + + Use SOCKS proxy + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Use SimpleX Chat servers? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Use chat @@ -7040,6 +8303,16 @@ To connect, please ask your contact to create another connection link and check Use current profile No comment provided by engineer. + + Use for files + Use for files + No comment provided by engineer. + + + Use for messages + Use for messages + No comment provided by engineer. + Use for new connections Use for new connections @@ -7080,6 +8353,16 @@ To connect, please ask your contact to create another connection link and check Use server No comment provided by engineer. + + Use servers + Use servers + No comment provided by engineer. + + + Use short links (BETA) + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Use the app while in the call. @@ -7090,9 +8373,9 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. - - User profile - User profile + + Use web port + Use web port No comment provided by engineer. @@ -7100,6 +8383,11 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + Username + No comment provided by engineer. + Using SimpleX Chat servers. Using SimpleX Chat servers. @@ -7170,11 +8458,21 @@ To connect, please ask your contact to create another connection link and check Videos and files up to 1gb No comment provided by engineer. + + View conditions + View conditions + No comment provided by engineer. + View security code View security code No comment provided by engineer. + + View updated conditions + View updated conditions + No comment provided by engineer. + Visible history Visible history @@ -7190,9 +8488,9 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. - Voice messages are prohibited in this group. + + Voice messages are prohibited. + Voice messages are prohibited. No comment provided by engineer. @@ -7285,9 +8583,9 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - When people request to connect, you can accept or reject it. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7333,7 +8631,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8658,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - You - You - No comment provided by engineer. - You **must not** use the same database on two devices. You **must not** use the same database on two devices. @@ -7390,6 +8683,11 @@ To connect, please ask your contact to create another connection link and check You are already connected to %@. No comment provided by engineer. + + You are already connected with %@. + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. You are already connecting to %@. @@ -7452,6 +8750,11 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure servers via settings. + You can configure servers via settings. + No comment provided by engineer. + You can create it later You can create it later @@ -7492,6 +8795,11 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. You can set lock screen notification preview via settings. @@ -7507,11 +8815,6 @@ Repeat join request? You can share this address with your contacts to let them connect with **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - You can share your address as a link or QR code - anybody can connect to you. - 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 @@ -7535,23 +8838,23 @@ Repeat join request? You can view invitation link again in connection details. You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. 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 via this address! You have already requested connection via this address! @@ -7619,6 +8922,11 @@ Repeat connection request? You sent group invitation No comment provided by engineer. + + You should receive notifications. + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! You will be connected to group when the group host's device is online, please wait or check later! @@ -7654,6 +8962,11 @@ 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 chat. Chat history will be preserved. + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. You will stop receiving messages from this group. Chat history will be preserved. @@ -7674,31 +8987,16 @@ Repeat connection request? You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed No comment provided by engineer. - - Your %@ servers - Your %@ servers - No comment provided by engineer. - Your ICE servers Your ICE servers No comment provided by engineer. - - Your SMP servers - Your SMP servers - No comment provided by engineer. - Your SimpleX address Your SimpleX address No comment provided by engineer. - - Your XFTP servers - Your XFTP servers - No comment provided by engineer. - Your calls Your calls @@ -7714,11 +9012,21 @@ Repeat connection request? Your chat database is not encrypted - set passphrase to encrypt it. No comment provided by engineer. + + Your chat preferences + Your chat preferences + alert title + Your chat profiles Your chat profiles No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Your contact sent a file that is larger than currently supported maximum size (%@). @@ -7734,6 +9042,11 @@ Repeat connection request? Your contacts will remain connected. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Your current chat database will be DELETED and REPLACED with the imported one. @@ -7764,33 +9077,36 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. + + Your profile is stored on your device and only shared with your contacts. + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Your profile, contacts and delivered messages are stored on your device. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. No comment provided by engineer. + + 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. + alert message + Your random profile Your random profile No comment provided by engineer. - - Your server - Your server - No comment provided by engineer. - Your server address Your server address No comment provided by engineer. + + Your servers + Your servers + No comment provided by engineer. + Your settings Your settings @@ -7831,6 +9147,11 @@ SimpleX servers cannot see your profile. accepted call call status + + accepted invitation + accepted invitation + chat list item title + admin admin @@ -7866,6 +9187,11 @@ SimpleX servers cannot see your profile. and %lld other events No comment provided by engineer. + + archived report + archived report + No comment provided by engineer. + attempts attempts @@ -7904,7 +9230,8 @@ SimpleX servers cannot see your profile. blocked by admin blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9346,7 @@ SimpleX servers cannot see your profile. connecting… connecting… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9401,8 @@ SimpleX servers cannot see your profile. default (%@) default (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9529,6 @@ SimpleX servers cannot see your profile. error No comment provided by engineer. - - event happened - event happened - No comment provided by engineer. - expired expired @@ -8376,20 +9699,20 @@ SimpleX servers cannot see your profile. moderated by %@ marked deleted chat item preview text + + moderator + moderator + member role + months months time unit - - mute - mute - No comment provided by engineer. - never never - No comment provided by engineer. + delete after time new message @@ -8420,8 +9743,8 @@ SimpleX servers cannot see your profile. off off enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9786,16 @@ SimpleX servers cannot see your profile. peer-to-peer No comment provided by engineer. + + pending + pending + No comment provided by engineer. + + + pending approval + pending approval + No comment provided by engineer. + quantum resistant e2e encryption quantum resistant e2e encryption @@ -8478,6 +9811,11 @@ SimpleX servers cannot see your profile. received confirmation… No comment provided by engineer. + + rejected + rejected + No comment provided by engineer. + rejected call rejected call @@ -8508,6 +9846,11 @@ SimpleX servers cannot see your profile. removed you rcv group event chat item + + requested to connect + requested to connect + chat list item title + saved saved @@ -8607,11 +9950,6 @@ last received msg: %2$@ unknown status No comment provided by engineer. - - unmute - unmute - No comment provided by engineer. - unprotected unprotected @@ -8776,7 +10114,7 @@ last received msg: %2$@
- +
@@ -8813,7 +10151,7 @@ last received msg: %2$@
- +
@@ -8833,9 +10171,41 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + %d new events + notification body + + + From %d chat(s) + From %d chat(s) + notification body + + + From: %@ + From: %@ + notification body + + + New events + New events + notification + + + New messages + New messages + notification + + +
- +
@@ -8857,7 +10227,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/en.xcloc/contents.json b/apps/ios/SimpleX Localizations/en.xcloc/contents.json index 2f39a1f1ee..ec2accf27e 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/en.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "en", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 6e5ec0e85a..d39fb61249 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (puede copiarse) @@ -127,6 +100,16 @@ %@ está verificado No comment provided by engineer. + + %@ server + %@ servidor + No comment provided by engineer. + + + %@ servers + %@ servidores + No comment provided by engineer. + %@ uploaded %@ subido @@ -137,6 +120,11 @@ ¡ %@ quiere contactar! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ y %lld miembro(s) más @@ -154,37 +142,67 @@ %d days - %d días + %d día(s) time interval + + %d file(s) are still being downloaded. + %d archivo(s) se está(n) descargando todavía. + forward confirmation reason + + + %d file(s) failed to download. + La descarga ha fallado para %d archivo(s). + forward confirmation reason + + + %d file(s) were deleted. + %d archivo(s) ha(n) sido eliminado(s). + forward confirmation reason + + + %d file(s) were not downloaded. + %d archivo(s) no se ha(n) descargado. + forward confirmation reason + %d hours - %d horas + %d hora(s) time interval + + %d messages not forwarded + %d mensaje(s) no enviado(s) + alert title + %d min - %d minutos + %d minuto(s) time interval %d months - %d meses + %d mes(es) time interval %d sec - %d segundos + %d segundo(s) time interval + + %d seconds(s) + %d segundos + delete after time + %d skipped message(s) - %d mensaje(s) saltado(s + %d mensaje(s) omitido(s) integrity error chat item %d weeks - %d semanas + %d semana(s) time interval @@ -247,11 +265,6 @@ %lld idiomas de interfaz nuevos No comment provided by engineer. - - %lld second(s) - %lld segundo(s) - No comment provided by engineer. - %lld seconds %lld segundos @@ -302,11 +315,6 @@ %u mensaje(s) omitido(s). No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nuevo) @@ -317,19 +325,9 @@ (este dispositivo v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Añadir contacto**: crea un enlace de invitación nuevo o usa un enlace recibido. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Añadir nuevo contacto**: para crear tu código QR o enlace de un uso para tu contacto. + + **Create 1-time link**: to create and share a new invitation link. + **Añadir contacto**: crea un enlace de invitación nuevo. No comment provided by engineer. @@ -337,13 +335,13 @@ **Crear grupo**: crea un grupo nuevo. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Más privado**: comprueba los mensajes nuevos cada 20 minutos. El token del dispositivo se comparte con el servidor de SimpleX Chat, pero no cuántos contactos o mensajes tienes. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación). No comment provided by engineer. @@ -354,14 +352,19 @@ **Please note**: you will NOT be able to recover or change passphrase if you lose it. - **Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes. + **Atención**: Si la pierdes NO podrás recuperar o cambiar la contraseña. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia. 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. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain. @@ -387,11 +390,6 @@ \*bold* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - historial de edición. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 seg @@ -446,7 +439,8 @@ 1 day un dia - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month un mes - time interval + delete after time +time interval 1 week una semana - time interval + delete after time +time interval + + + 1 year + 1 año + delete after time + + + 1-time link + Enlace de un uso + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Los enlaces de un uso pueden ser usados *solamente con un contacto* - compártelos en persona o mediante cualquier aplicación de mensajería. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 segundos No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -507,7 +513,7 @@ A new random profile will be shared. - Se compartirá un perfil nuevo aleatorio. + Compartirás un perfil nuevo aleatorio. No comment provided by engineer. @@ -519,7 +525,7 @@ 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. Se usará una conexión TCP independiente **por cada contacto y miembro de grupo**. -**Atención**: si tienes muchas conexiones, tu consumo de batería y tráfico pueden ser sustancialmente mayores y algunas conexiones pueden fallar. +**Atención**: si tienes muchas conexiones, tu consumo de batería y tráfico pueden aumentar bastante y algunas conexiones pueden fallar. No comment provided by engineer. @@ -537,19 +543,14 @@ ¿Cancelar el cambio de servidor? No comment provided by engineer. - - About SimpleX - Acerca de SimpleX - No comment provided by engineer. - About SimpleX Chat Sobre SimpleX Chat No comment provided by engineer. - - About SimpleX address - Acerca de la dirección SimpleX + + About operators + Acerca de los operadores No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Aceptar accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Aceptar condiciones + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Aceptar incógnito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Condiciones aceptadas + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Errores de confirmación No comment provided by engineer. + + Active + Activo + token status text + Active connections Conexiones activas @@ -600,14 +616,14 @@ 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. No comment provided by engineer. - - Add contact - Añadir contacto + + Add friends + Añadir amigos No comment provided by engineer. - - Add preset servers - Añadir servidores predefinidos + + Add list + Añadir lista No comment provided by engineer. @@ -625,16 +641,41 @@ Añadir servidores mediante el escaneo de códigos QR. No comment provided by engineer. + + Add team members + Añadir miembros del equipo + No comment provided by engineer. + Add to another device Añadir a otro dispositivo No comment provided by engineer. + + Add to list + Añadir a la lista + No comment provided by engineer. + Add welcome message Añadir mensaje de bienvenida No comment provided by engineer. + + Add your team members to the conversations. + Añade a miembros de tu equipo a las conversaciones. + No comment provided by engineer. + + + Added media & file servers + Servidores de archivos y multimedia añadidos + No comment provided by engineer. + + + Added message servers + Servidores de mensajes añadidos + No comment provided by engineer. + Additional accent Acento adicional @@ -660,6 +701,16 @@ El cambio de dirección se cancelará. Se usará la antigua dirección de recepción. No comment provided by engineer. + + Address or 1-time link? + ¿Dirección o enlace de un uso? + No comment provided by engineer. + + + Address settings + Configurar dirección + No comment provided by engineer. + Admins can block a member for all. Los administradores pueden bloquear a un miembro para los demás. @@ -680,6 +731,11 @@ Configuración avanzada No comment provided by engineer. + + All + Todo + No comment provided by engineer. + All app data is deleted. Todos los datos de la aplicación se eliminarán. @@ -687,16 +743,21 @@ All chats and messages will be deleted - this cannot be undone! - Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse! + Se eliminarán todos los chats y mensajes. ¡No puede deshacerse! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Todos los chats se quitarán de la lista %@ y esta será eliminada. + alert message + All data is erased when it is entered. Al introducirlo todos los datos son eliminados. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Todos los datos son privados y están en tu dispositivo. No comment provided by engineer. @@ -705,14 +766,19 @@ Todos los miembros del grupo permanecerán conectados. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! - Todos los mensajes serán borrados. ¡No podrá deshacerse! + Todos los mensajes serán eliminados. ¡No puede deshacerse! No comment provided by engineer. All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. - Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse! + Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse! No comment provided by engineer. @@ -723,6 +789,16 @@ All profiles Todos los perfiles + profile dropdown + + + All reports will be archived for you. + Todos los informes serán archivados para ti. + No comment provided by engineer. + + + All servers + Todos los servidores No comment provided by engineer. @@ -800,6 +876,11 @@ Se permite la eliminación irreversible de mensajes. (24 horas) No comment provided by engineer. + + Allow to report messsages to moderators. + Permitir informar de mensajes a los moderadores. + No comment provided by engineer. + Allow to send SimpleX links. Se permite enviar enlaces SimpleX. @@ -880,11 +961,21 @@ Se creará un perfil vacío con el nombre proporcionado, y la aplicación se abrirá como de costumbre. No comment provided by engineer. + + Another reason + Otro motivo + report reason + 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: %@ @@ -900,9 +991,14 @@ Cifrado de los nuevos archivos locales (excepto vídeos). No comment provided by engineer. + + App group: + Grupo app: + No comment provided by engineer. + App icon - Icono aplicación + Icono de la aplicación No comment provided by engineer. @@ -915,6 +1011,11 @@ El código de acceso será reemplazado por código de autodestrucción. No comment provided by engineer. + + App session + por sesión + No comment provided by engineer. + App version Versión de la aplicación @@ -940,6 +1041,21 @@ Aplicar a No comment provided by engineer. + + Archive + Archivar + No comment provided by engineer. + + + Archive %lld reports? + ¿Archivar %lld informes? + No comment provided by engineer. + + + Archive all reports? + ¿Archivar todos los informes? + No comment provided by engineer. + Archive and upload Archivar y subir @@ -950,6 +1066,21 @@ Archiva contactos para charlar más tarde. No comment provided by engineer. + + Archive report + Archivar informe + No comment provided by engineer. + + + Archive report? + ¿Archivar informe? + No comment provided by engineer. + + + Archive reports + Archivar informes + swipe action + Archived contacts Contactos archivados @@ -1020,6 +1151,11 @@ Aceptar imágenes automáticamente No comment provided by engineer. + + Auto-accept settings + Auto aceptar configuración + alert title + Back Volver @@ -1045,11 +1181,26 @@ Hash de mensaje incorrecto No comment provided by engineer. + + Better calls + Llamadas mejoradas + No comment provided by engineer. + Better groups Grupos mejorados No comment provided by engineer. + + Better groups performance + Rendimiento de grupos mejorado + No comment provided by engineer. + + + Better message dates. + Sistema de fechas mejorado. + No comment provided by engineer. + Better messages Mensajes mejorados @@ -1060,6 +1211,26 @@ Uso de red mejorado No comment provided by engineer. + + Better notifications + Notificaciones mejoradas + No comment provided by engineer. + + + Better privacy and security + Privacidad y seguridad mejoradas + No comment provided by engineer. + + + Better security ✅ + Seguridad mejorada ✅ + No comment provided by engineer. + + + Better user experience + Experiencia de usuario mejorada + No comment provided by engineer. + Black Negro @@ -1140,11 +1311,35 @@ 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)! No comment provided by engineer. + + Business address + Dirección empresarial + No comment provided by engineer. + + + Business chats + Chats empresariales + No comment provided by engineer. + + + Businesses + Empresas + 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: +- 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! @@ -1193,7 +1388,8 @@ Cancel Cancelar - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1409,7 @@ Cannot receive file No se puede recibir el archivo - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1426,16 @@ Cambiar No comment provided by engineer. + + Change automatic message deletion? + ¿Modificar la eliminación automática de mensajes? + alert title + + + Change chat profiles + Cambiar perfil de usuario + authentication reason + Change database passphrase? ¿Cambiar contraseña de la base de datos? @@ -1274,11 +1480,21 @@ Change self-destruct passcode Cambiar código autodestrucción authentication reason - set passcode view +set passcode view - - Chat archive - Archivo del chat + + Chat + Chat + No comment provided by engineer. + + + Chat already exists + El chat ya existe + No comment provided by engineer. + + + Chat already exists! + ¡El chat ya existe! No comment provided by engineer. @@ -1341,20 +1557,50 @@ Preferencias de Chat No comment provided by engineer. + + Chat preferences were changed. + Las preferencias del chat han sido modificadas. + alert message + + + Chat profile + Perfil de usuario + No comment provided by engineer. + Chat theme Tema de chat No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + El chat será eliminado para todos los miembros. ¡No puede deshacerse! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + El chat será eliminado para tí. ¡No puede deshacerse! + No comment provided by engineer. + Chats Chats 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. + No comment provided by engineer. + Check server address and try again. Comprueba la dirección del servidor e inténtalo de nuevo. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,9 +1652,19 @@ ¿Vaciar conversación? No comment provided by engineer. + + Clear group? + ¿Vaciar grupo? + No comment provided by engineer. + + + Clear or delete group? + ¿Vaciar o eliminar grupo? + No comment provided by engineer. + Clear private notes? - ¿Borrar notas privadas? + ¿Eliminar notas privadas? No comment provided by engineer. @@ -1426,6 +1682,11 @@ Modo de color No comment provided by engineer. + + Community guidelines violation + Violación de las normas de la comunidad + report reason + Compare file Comparar archivo @@ -1441,14 +1702,49 @@ Completadas No comment provided by engineer. + + Conditions accepted on: %@. + Condiciones aceptadas el: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Las condiciones se han aceptado para el(los) operador(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**. + No comment provided by engineer. + + + Conditions of use + Condiciones de uso + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Las condiciones serán aceptadas para el/los operador(es): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Las condiciones serán aceptadas el: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %@. + No comment provided by engineer. + Configure ICE servers Configure servidores ICE No comment provided by engineer. - - Configured %@ servers - %@ servidores configurados + + Configure server operators + Configurar operadores de servidores No comment provided by engineer. @@ -1493,7 +1789,7 @@ Confirm that you remember database passphrase to migrate it. - Para migrar confirma que recuerdas la frase de contraseña de la base de datos. + Para migrar la base de datos confirma que recuerdas la frase de contraseña. No comment provided by engineer. @@ -1501,6 +1797,11 @@ Confirmar subida No comment provided by engineer. + + Confirmed + Confirmado + token status text + Connect Conectar @@ -1620,6 +1921,11 @@ This is your own one-time link! Estado de tu conexión y servidores. No comment provided by engineer. + + Connection blocked + Conexión bloqueada + No comment provided by engineer. + Connection error Error conexión @@ -1630,6 +1936,18 @@ This is your own one-time link! Error de conexión (Autenticación) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + Conexión bloqueada por el operador del servidor: +%@ + No comment provided by engineer. + + + Connection not ready. + Conexión no establecida. + No comment provided by engineer. + Connection notifications Notificaciones de conexión @@ -1640,6 +1958,16 @@ This is your own one-time link! ¡Solicitud de conexión enviada! No comment provided by engineer. + + Connection requires encryption renegotiation. + La conexión requiere renegociar el cifrado. + No comment provided by engineer. + + + Connection security + Seguridad de conexión + No comment provided by engineer. + Connection terminated Conexión finalizada @@ -1702,7 +2030,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! - El contacto será eliminado. ¡No podrá deshacerse! + El contacto será eliminado. ¡No puede deshacerse! No comment provided by engineer. @@ -1715,6 +2043,11 @@ This is your own one-time link! Tus contactos sólo pueden marcar los mensajes para eliminar. Tu podrás verlos. No comment provided by engineer. + + Content violates conditions of use + El contenido viola las condiciones de uso + blocking reason + Continue Continuar @@ -1740,6 +2073,11 @@ This is your own one-time link! Versión Core: v%@ No comment provided by engineer. + + Corner + Esquina + No comment provided by engineer. + Correct name to %@? ¿Corregir el nombre a %@? @@ -1750,6 +2088,11 @@ This is your own one-time link! Crear No comment provided by engineer. + + Create 1-time link + Crear enlace de un uso + No comment provided by engineer. + Create SimpleX address Crear dirección SimpleX @@ -1760,11 +2103,6 @@ This is your own one-time link! Crear grupo usando perfil aleatorio. No comment provided by engineer. - - Create an address to let people connect with you. - Crea una dirección para que otras personas puedan conectar contigo. - No comment provided by engineer. - Create file Crear archivo @@ -1785,6 +2123,11 @@ This is your own one-time link! Crear enlace No comment provided by engineer. + + Create list + Crear lista + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Crea perfil nuevo en la [aplicación para PC](https://simplex.Descargas/de chat/). 💻 @@ -1825,11 +2168,6 @@ This is your own one-time link! Creado: %@ copied message info - - Created on %@ - Creado en %@ - No comment provided by engineer. - Creating archive link Creando enlace al archivo @@ -1845,6 +2183,11 @@ This is your own one-time link! Código de Acceso No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace: + No comment provided by engineer. + Current passphrase… Contraseña actual… @@ -1865,6 +2208,11 @@ This is your own one-time link! Tiempo personalizado No comment provided by engineer. + + Customizable message shape. + Forma personalizable de los mensajes. + No comment provided by engineer. + Customize theme Personalizar tema @@ -1946,7 +2294,7 @@ This is your own one-time link! Database passphrase is different from saved in the keychain. - La contraseña es distinta a la almacenada en Keychain. + La contraseña es diferente a la almacenada en Keychain. No comment provided by engineer. @@ -1996,8 +2344,8 @@ This is your own one-time link! Delete Eliminar - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2382,14 @@ This is your own one-time link! Eliminar y notificar contacto No comment provided by engineer. - - Delete archive - Eliminar archivo + + Delete chat + Eliminar chat No comment provided by engineer. - - Delete chat archive? - ¿Eliminar archivo del chat? + + Delete chat messages from your device. + Elimina los mensajes del dispositivo. No comment provided by engineer. @@ -2054,6 +2402,11 @@ This is your own one-time link! ¿Eliminar perfil? No comment provided by engineer. + + Delete chat? + ¿Eliminar chat? + No comment provided by engineer. + Delete connection Eliminar conexión @@ -2086,7 +2439,7 @@ This is your own one-time link! Delete files and media? - Eliminar archivos y multimedia? + ¿Eliminar archivos y multimedia? No comment provided by engineer. @@ -2129,6 +2482,11 @@ This is your own one-time link! ¿Eliminar enlace? No comment provided by engineer. + + Delete list? + ¿Eliminar lista? + alert title + Delete member message? ¿Eliminar el mensaje de miembro? @@ -2141,8 +2499,8 @@ This is your own one-time link! Delete messages - Eliminar mensaje - No comment provided by engineer. + Activar + alert button Delete messages after @@ -2159,6 +2517,11 @@ This is your own one-time link! ¿Eliminar base de datos antigua? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Elimina o modera hasta 200 mensajes a la vez. + No comment provided by engineer. + Delete pending connection? ¿Eliminar conexión pendiente? @@ -2174,6 +2537,11 @@ This is your own one-time link! Eliminar cola server test step + + Delete report + Eliminar informe + No comment provided by engineer. + Delete up to 20 messages at once. Elimina hasta 20 mensajes a la vez. @@ -2209,6 +2577,11 @@ This is your own one-time link! Errores de eliminación No comment provided by engineer. + + Delivered even when Apple drops them. + Entregados incluso cuando Apple los descarta. + No comment provided by engineer. + Delivery Entrega @@ -2309,8 +2682,13 @@ This is your own one-time link! Mensajes directos chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + Mensajes directos no permitidos entre miembros de este chat. + 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. @@ -2324,6 +2702,16 @@ This is your own one-time link! Desactivar Bloqueo SimpleX authentication reason + + Disable automatic message deletion? + ¿Desactivar la eliminación automática de mensajes? + alert title + + + Disable delete messages + Desactivar + alert button + Disable for all Desactivar para todos @@ -2349,8 +2737,8 @@ This is your own one-time link! Los mensajes temporales no están permitidos en este chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Los mensajes temporales no están permitidos en este grupo. No comment provided by engineer. @@ -2371,7 +2759,7 @@ This is your own one-time link! Disconnect desktop? - ¿Desconectar ordenador? + ¿Desconectar del ordenador? No comment provided by engineer. @@ -2409,6 +2797,16 @@ This is your own one-time link! No se envía el historial a los miembros nuevos. No comment provided by engineer. + + Do not use credentials with proxy. + No uses credenciales con proxy. + No comment provided by engineer. + + + Documents: + Documentos: + No comment provided by engineer. + Don't create address No crear dirección SimpleX @@ -2419,9 +2817,19 @@ This is your own one-time link! No activar No comment provided by engineer. + + Don't miss important messages. + No pierdas los mensajes importantes. + No comment provided by engineer. + Don't show again - No mostrar de nuevo + No volver a mostrar + No comment provided by engineer. + + + Done + Hecho No comment provided by engineer. @@ -2432,7 +2840,8 @@ This is your own one-time link! Download Descargar - chat item action + alert button +chat item action Download errors @@ -2449,6 +2858,11 @@ This is your own one-time link! Descargar archivo server test step + + Download files + Descargar archivos + alert action + Downloaded Descargado @@ -2479,6 +2893,11 @@ This is your own one-time link! Duración No comment provided by engineer. + + E2E encrypted notifications. + Notificaciones cifradas E2E. + No comment provided by engineer. + Edit Editar @@ -2499,6 +2918,11 @@ This is your own one-time link! Activar (conservar anulaciones) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + Habilitar Flux en la configuración de Red y servidores para mejorar la privacidad de los metadatos. + No comment provided by engineer. + Enable SimpleX Lock Activar Bloqueo SimpleX @@ -2512,7 +2936,7 @@ This is your own one-time link! Enable automatic message deletion? ¿Activar eliminación automática de mensajes? - No comment provided by engineer. + alert title Enable camera access @@ -2581,7 +3005,7 @@ This is your own one-time link! Encrypt local files - Cifra archivos locales + Cifrar archivos locales No comment provided by engineer. @@ -2639,6 +3063,11 @@ This is your own one-time link! Renegociación de cifrado fallida. No comment provided by engineer. + + Encryption renegotiation in progress. + Renegociación de cifrado en curso. + No comment provided by engineer. + Enter Passcode Introduce Código @@ -2671,7 +3100,7 @@ This is your own one-time link! Enter server manually - Introduce el servidor manualmente + Añadir manualmente No comment provided by engineer. @@ -2704,26 +3133,36 @@ This is your own one-time link! Error al cancelar cambio de dirección No comment provided by engineer. + + Error accepting conditions + Error al aceptar las condiciones + alert title + Error accepting contact request Error al aceptar solicitud del contacto No comment provided by engineer. - - Error accessing database file - Error al acceder al archivo de la base de datos - No comment provided by engineer. - Error adding member(s) Error al añadir miembro(s) No comment provided by engineer. + + Error adding server + Error al añadir servidor + alert title + Error changing address Error al cambiar servidor No comment provided by engineer. + + Error changing connection profile + Error al cambiar el perfil de conexión + No comment provided by engineer. + Error changing role Error al cambiar rol @@ -2734,6 +3173,16 @@ This is your own one-time link! Error cambiando configuración No comment provided by engineer. + + Error changing to incognito! + ¡Error al cambiar a incógnito! + No comment provided by engineer. + + + Error checking token status + Error al verificar el estado del token + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Error al conectar con el servidor de reenvío %@. Por favor, inténtalo más tarde. @@ -2754,6 +3203,11 @@ This is your own one-time link! Error al crear enlace de grupo No comment provided by engineer. + + Error creating list + Error al crear lista + alert title + Error creating member contact Error al establecer contacto con el miembro @@ -2769,6 +3223,11 @@ This is your own one-time link! ¡Error al crear perfil! No comment provided by engineer. + + Error creating report + Error al crear informe + No comment provided by engineer. + Error decrypting file Error al descifrar el archivo @@ -2849,9 +3308,14 @@ This is your own one-time link! Error al unirte al grupo No comment provided by engineer. - - Error loading %@ servers - Error al cargar servidores %@ + + Error loading servers + Error al cargar servidores + alert title + + + Error migrating settings + Error al migrar la configuración No comment provided by engineer. @@ -2862,7 +3326,7 @@ This is your own one-time link! Error receiving file Error al recibir archivo - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3338,36 @@ This is your own one-time link! Error al reconectar con los servidores No comment provided by engineer. + + Error registering for notifications + Error al registrarse para notificaciones + alert title + Error removing member - Error al eliminar miembro + Error al expulsar miembro No comment provided by engineer. + + Error reordering lists + Error al reorganizar listas + alert title + Error resetting statistics Error al restablecer las estadísticas No comment provided by engineer. - - Error saving %@ servers - Error al guardar servidores %@ - No comment provided by engineer. - Error saving ICE servers Error al guardar servidores ICE No comment provided by engineer. + + Error saving chat list + Error al guardar listas + alert title + Error saving group profile Error al guardar perfil de grupo @@ -2909,6 +3383,11 @@ This is your own one-time link! Error al guardar contraseña en Keychain No comment provided by engineer. + + Error saving servers + Error al guardar servidores + alert title + Error saving settings Error al guardar ajustes @@ -2954,16 +3433,26 @@ This is your own one-time link! Error al parar SimpleX No comment provided by engineer. + + Error switching profile + Error al cambiar perfil + No comment provided by engineer. + Error switching profile! ¡Error al cambiar perfil! - No comment provided by engineer. + alertTitle Error synchronizing connection Error al sincronizar conexión No comment provided by engineer. + + Error testing server connection + Error al testar la conexión al servidor + No comment provided by engineer. + Error updating group link Error al actualizar enlace de grupo @@ -2974,6 +3463,11 @@ This is your own one-time link! Error al actualizar mensaje No comment provided by engineer. + + Error updating server + Error al actualizar el servidor + alert title + Error updating settings Error al actualizar configuración @@ -3002,8 +3496,9 @@ This is your own one-time link! Error: %@ Error: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3515,11 @@ This is your own one-time link! Errores No comment provided by engineer. + + Errors in servers configuration. + Error en la configuración del servidor. + servers error + Even when disabled in the conversation. Incluso si está desactivado para la conversación. @@ -3035,6 +3535,11 @@ This is your own one-time link! Expandir chat item action + + Expired + Expirado + token status text + Export database Exportar base de datos @@ -3075,24 +3580,53 @@ This is your own one-time link! ¡Rápido y sin necesidad de esperar a que el remitente esté en línea! No comment provided by engineer. + + Faster deletion of groups. + Eliminación más rápida de grupos. + No comment provided by engineer. + Faster joining and more reliable messages. Mensajería más segura y conexión más rápida. No comment provided by engineer. + + Faster sending messages. + Envío más rápido de mensajes. + No comment provided by engineer. + Favorite Favoritos swipe action + + Favorites + Favoritos + No comment provided by engineer. + File error Error de archivo - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Error(es) de archivo +%@ + alert message + + + File is blocked by server operator: +%@. + Archivo bloqueado por el operador del servidor +%@. + file error text File not found - most likely file was deleted or cancelled. - Archivo no encontrado, probablemente haya sido borrado o cancelado. + Archivo no encontrado, probablemente haya sido eliminado o cancelado. file error text @@ -3145,8 +3679,8 @@ This is your own one-time link! Archivos y multimedia chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Los archivos y multimedia no están permitidos en este grupo. No comment provided by engineer. @@ -3215,21 +3749,71 @@ This is your own one-time link! Corrección no compatible con miembro del grupo No comment provided by engineer. + + For all moderators + Para todos los moderadores + No comment provided by engineer. + + + For chat profile %@: + Para el perfil de chat %@: + servers error + For console Para consola No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Si por ejemplo tu contacto recibe los mensajes a través de un servidor de SimpleX Chat, tu aplicación los entregará a través de un servidor de Flux. + No comment provided by engineer. + + + For me + para mí + No comment provided by engineer. + + + For private routing + Para enrutamiento privado + No comment provided by engineer. + + + For social media + Para redes sociales + No comment provided by engineer. + Forward Reenviar chat item action + + Forward %d message(s)? + ¿Reenviar %d mensaje(s)? + alert title + Forward and save messages Reenviar y guardar mensajes No comment provided by engineer. + + Forward messages + Reenviar mensajes + alert action + + + Forward messages without files? + ¿Reenviar mensajes sin los archivos? + alert message + + + Forward up to 20 messages at once. + Desplazamiento de hasta 20 mensajes. + No comment provided by engineer. + Forwarded Reenviado @@ -3240,6 +3824,11 @@ This is your own one-time link! Reenviado por No comment provided by engineer. + + Forwarding %lld messages + Reenviando %lld mensajes + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. El servidor de reenvío %@ no ha podido conectarse al servidor de destino %@. Por favor, intentalo más tarde. @@ -3289,19 +3878,14 @@ Error: %2$@ Nombre completo (opcional) No comment provided by engineer. - - Full name: - Nombre completo: - No comment provided by engineer. - Fully decentralized – visible only to members. - Completamente descentralizado y sólo visible para los miembros. + Totalmente descentralizado. Visible sólo para los miembros. No comment provided by engineer. Fully re-implemented - work in background! - Completamente reimplementado: ¡funciona en segundo plano! + Totalmente revisado. ¡Funciona en segundo plano! No comment provided by engineer. @@ -3314,6 +3898,11 @@ Error: %2$@ GIFs y stickers No comment provided by engineer. + + Get notified when mentioned. + Las menciones ahora se notifican. + No comment provided by engineer. + Good afternoon! ¡Buenas tardes! @@ -3379,41 +3968,6 @@ Error: %2$@ Enlaces de grupo No comment provided by engineer. - - Group members can add message reactions. - Los miembros pueden añadir reacciones a los mensajes. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) - No comment provided by engineer. - - - Group members can send SimpleX links. - Los miembros del grupo pueden enviar enlaces SimpleX. - No comment provided by engineer. - - - Group members can send direct messages. - Los miembros del grupo pueden enviar mensajes directos. - No comment provided by engineer. - - - Group members can send disappearing messages. - Los miembros del grupo pueden enviar mensajes temporales. - No comment provided by engineer. - - - Group members can send files and media. - Los miembros del grupo pueden enviar archivos y multimedia. - No comment provided by engineer. - - - Group members can send voice messages. - Los miembros del grupo pueden enviar mensajes de voz. - No comment provided by engineer. - Group message: Mensaje de grupo: @@ -3446,12 +4000,17 @@ Error: %2$@ Group will be deleted for all members - this cannot be undone! - El grupo será eliminado para todos los miembros. ¡No podrá deshacerse! + El grupo será eliminado para todos los miembros. ¡No puede deshacerse! No comment provided by engineer. Group will be deleted for you - this cannot be undone! - El grupo será eliminado para tí. ¡No podrá deshacerse! + El grupo será eliminado para tí. ¡No puede deshacerse! + No comment provided by engineer. + + + Groups + Grupos No comment provided by engineer. @@ -3459,6 +4018,11 @@ Error: %2$@ Ayuda No comment provided by engineer. + + Help admins moderating their groups. + Ayuda a los admins a moderar sus grupos. + No comment provided by engineer. + Hidden Oculto @@ -3509,10 +4073,20 @@ Error: %2$@ Cómo funciona SimpleX No comment provided by engineer. + + How it affects privacy + Cómo afecta a la privacidad + No comment provided by engineer. + + + How it helps privacy + Cómo ayuda a la privacidad + No comment provided by engineer. + How it works Cómo funciona - No comment provided by engineer. + alert button How to @@ -3539,6 +4113,11 @@ Error: %2$@ Servidores ICE (uno por línea) No comment provided by engineer. + + IP address + Dirección IP + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace. @@ -3579,8 +4158,8 @@ Error: %2$@ Inmediatamente No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Inmune a spam y abuso No comment provided by engineer. @@ -3614,6 +4193,13 @@ Error: %2$@ Importando archivo No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Reducción del tráfico y entrega mejorada. +¡Pronto habrá nuevas mejoras! + No comment provided by engineer. + Improved message delivery Entrega de mensajes mejorada @@ -3644,6 +4230,16 @@ Error: %2$@ Sonido de llamada No comment provided by engineer. + + Inappropriate content + Contenido inapropiado + report reason + + + Inappropriate profile + Perfil inapropiado + report reason + Incognito Incógnito @@ -3714,6 +4310,11 @@ Error: %2$@ Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Al instante + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4322,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Al instante - No comment provided by engineer. - Interface Interfaz @@ -3736,6 +4332,31 @@ Error: %2$@ Colores del interfaz No comment provided by engineer. + + Invalid + No válido + token status text + + + Invalid (bad token) + No válido (token incorrecto) + token status text + + + Invalid (expired) + No válido (expirado) + token status text + + + Invalid (unregistered) + No válido (no registrado) + token status text + + + Invalid (wrong topic) + No válido (tópico incorrecto) + token status text + Invalid QR code Código QR no válido @@ -3774,7 +4395,7 @@ Error: %2$@ Invalid server address! ¡Dirección de servidor no válida! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4417,11 @@ Error: %2$@ Invitar miembros No comment provided by engineer. + + Invite to chat + Invitar al chat + No comment provided by engineer. + Invite to group Invitar al grupo @@ -3811,8 +4437,8 @@ Error: %2$@ La eliminación irreversible de mensajes no está permitida en este chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. La eliminación irreversible de mensajes no está permitida en este grupo. No comment provided by engineer. @@ -3902,7 +4528,7 @@ This is your link for group %@! Keep Guardar - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4543,7 @@ This is your link for group %@! Keep unused invitation? ¿Guardar invitación no usada? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4580,16 @@ This is your link for group %@! Salir swipe action + + Leave chat + Salir del chat + No comment provided by engineer. + + + Leave chat? + ¿Salir del chat? + No comment provided by engineer. + Leave group Salir del grupo @@ -3994,6 +4630,21 @@ This is your link for group %@! Ordenadores enlazados No comment provided by engineer. + + List + Lista + swipe action + + + List name and emoji should be different for all lists. + El nombre y el emoji deben ser diferentes en todas las listas. + No comment provided by engineer. + + + List name... + Nombre de la lista... + No comment provided by engineer. + Live message! ¡Mensaje en vivo! @@ -4004,11 +4655,6 @@ This is your link for group %@! Mensajes en vivo No comment provided by engineer. - - Local - Local - No comment provided by engineer. - Local name Nombre local @@ -4016,7 +4662,7 @@ This is your link for group %@! Local profile data only - Sólo datos del perfil local + Eliminar sólo el perfil No comment provided by engineer. @@ -4029,11 +4675,6 @@ This is your link for group %@! Modo bloqueo No comment provided by engineer. - - Make a private connection - Establecer una conexión privada - No comment provided by engineer. - Make one message disappear Escribir un mensaje temporal @@ -4044,21 +4685,11 @@ This is your link for group %@! ¡Hacer perfil privado! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Asegúrate de que las direcciones del servidor %@ tienen el formato correcto, están separadas por líneas y no duplicadas (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Asegúrate de que las direcciones del servidor WebRTC ICE tienen el formato correcto, están separadas por líneas y no duplicadas. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Muchos se preguntarán: *si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes?* - No comment provided by engineer. - Mark deleted for everyone Marcar como eliminado para todos @@ -4104,6 +4735,16 @@ This is your link for group %@! Miembro inactivo item status text + + Member reports + Informes de miembros + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + El rol del miembro cambiará a "%@" y todos serán notificados. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. El rol del miembro cambiará a "%@" y se notificará al grupo. @@ -4114,9 +4755,59 @@ This is your link for group %@! El rol del miembro cambiará a "%@" y recibirá una invitación nueva. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + El miembro será eliminado del chat. ¡No puede deshacerse! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! - El miembro será expulsado del grupo. ¡No podrá deshacerse! + El miembro será expulsado del grupo. ¡No puede deshacerse! + 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 irreversibly delete sent messages. (24 hours) + Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) + No comment provided by engineer. + + + Members can report messsages to moderators. + Los miembros pueden informar de mensajes a los moderadores. + No comment provided by engineer. + + + Members can send SimpleX links. + Los miembros del grupo pueden enviar enlaces SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Los miembros del grupo pueden enviar mensajes directos. + No comment provided by engineer. + + + Members can send disappearing messages. + Los miembros del grupo pueden enviar mensajes temporales. + No comment provided by engineer. + + + Members can send files and media. + Los miembros del grupo pueden enviar archivos y multimedia. + No comment provided by engineer. + + + Members can send voice messages. + Los miembros del grupo pueden enviar mensajes de voz. + No comment provided by engineer. + + + Mention members 👋 + Menciona a miembros 👋 No comment provided by engineer. @@ -4169,8 +4860,8 @@ This is your link for group %@! Las reacciones a los mensajes no están permitidas en este chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Las reacciones a los mensajes no están permitidas en este grupo. No comment provided by engineer. @@ -4184,6 +4875,11 @@ This is your link for group %@! Servidores de mensajes No comment provided by engineer. + + Message shape + Forma del mensaje + No comment provided by engineer. + Message source remains private. El autor del mensaje se mantiene privado. @@ -4224,6 +4920,11 @@ This is your link for group %@! ¡Los mensajes de %@ serán mostrados! No comment provided by engineer. + + Messages in this chat will never be deleted. + Los mensajes de esta conversación nunca se eliminan. + alert message + Messages received Mensajes recibidos @@ -4234,6 +4935,11 @@ This is your link for group %@! Mensajes enviados No comment provided by engineer. + + Messages were deleted after you selected them. + Los mensajes han sido eliminados después de seleccionarlos. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo** con secreto perfecto hacía adelante, repudio y recuperación tras ataque. @@ -4299,9 +5005,9 @@ This is your link for group %@! Migración completada No comment provided by engineer. - - Migrations: %@ - Migraciones: %@ + + Migrations: + Migraciones: No comment provided by engineer. @@ -4319,6 +5025,11 @@ This is your link for group %@! Moderado: %@ copied message info + + More + Más + swipe action + More improvements are coming soon! ¡Pronto habrá más mejoras! @@ -4329,6 +5040,11 @@ This is your link for group %@! Conexión de red más fiable. No comment provided by engineer. + + More reliable notifications + Notificaciones más fiables + No comment provided by engineer. + Most likely this connection is deleted. Probablemente la conexión ha sido eliminada. @@ -4342,7 +5058,12 @@ This is your link for group %@! Mute Silenciar - swipe action + notification label action + + + Mute all + Silenciar todo + notification label action Muted when inactive! @@ -4364,6 +5085,11 @@ This is your link for group %@! Conexión de red No comment provided by engineer. + + Network decentralization + Descentralización de la red + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Problema en la red - el mensaje ha expirado tras muchos intentos de envío. @@ -4374,6 +5100,11 @@ This is your link for group %@! Gestión de la red No comment provided by engineer. + + Network operator + Operador de red + No comment provided by engineer. + Network settings Configuración de red @@ -4384,11 +5115,26 @@ This is your link for group %@! Estado de la red No comment provided by engineer. + + New + Nuevo + token status text + New Passcode Código Nuevo No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Se usarán credenciales SOCKS nuevas cada vez que inicies la aplicación. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Se usarán credenciales SOCKS nuevas para cada servidor. + No comment provided by engineer. + New chat Nuevo chat @@ -4409,11 +5155,6 @@ This is your link for group %@! Contacto nuevo: notification - - New database archive - Nuevo archivo de bases de datos - No comment provided by engineer. - New desktop app! Nueva aplicación para PC! @@ -4424,6 +5165,11 @@ This is your link for group %@! Nuevo nombre mostrado No comment provided by engineer. + + New events + Eventos nuevos + notification + New in %@ Nuevo en %@ @@ -4449,6 +5195,11 @@ This is your link for group %@! Contraseña nueva… No comment provided by engineer. + + New server + Servidor nuevo + No comment provided by engineer. + No No @@ -4459,6 +5210,21 @@ This is your link for group %@! Sin contraseña de la aplicación Authentication unavailable + + No chats + Sin chats + No comment provided by engineer. + + + No chats found + Ningún chat encontrado + No comment provided by engineer. + + + No chats in list %@ + Sin chats en la lista %@ + No comment provided by engineer. + No contacts selected Ningún contacto seleccionado @@ -4504,31 +5270,106 @@ This is your link for group %@! No hay información, intenta recargar No comment provided by engineer. + + No media & file servers. + Sin servidores para archivos y multimedia. + servers error + + + No message + Ningún mensaje + No comment provided by engineer. + + + No message servers. + Sin servidores para mensajes. + servers error + No network connection Sin conexión de red No comment provided by engineer. + + No permission to record speech + Sin permiso para grabación de voz + No comment provided by engineer. + + + No permission to record video + Sin permiso para grabación de vídeo + No comment provided by engineer. + No permission to record voice message Sin permiso para grabar mensajes de voz No comment provided by engineer. + + No push server + Sin servidores push + No comment provided by engineer. + No received or sent files Sin archivos recibidos o enviados No comment provided by engineer. + + No servers for private message routing. + Sin servidores para enrutamiento privado. + servers error + + + No servers to receive files. + Sin servidores para recibir archivos. + servers error + + + No servers to receive messages. + Sin servidores para recibir mensajes. + servers error + + + No servers to send files. + Sin servidores para enviar archivos. + servers error + + + No token! + ¡Sin token! + alert title + + + No unread chats + Ningún chat sin leer + No comment provided by engineer. + + + No user identifiers. + Sin identificadores de usuario. + No comment provided by engineer. + Not compatible! ¡No compatible! No comment provided by engineer. + + Notes + Notas + No comment provided by engineer. + Nothing selected Nada seleccionado No comment provided by engineer. + + Nothing to forward! + ¡Nada para reenviar! + alert title + Notifications Notificaciones @@ -4539,6 +5380,21 @@ This is your link for group %@! ¡Las notificaciones están desactivadas! No comment provided by engineer. + + Notifications error + Error en notificaciones + alert title + + + Notifications privacy + Privacidad en las notificaciones + No comment provided by engineer. + + + Notifications status + Estado notificaciones + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5417,13 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database Base de datos antigua No comment provided by engineer. - - Old database archive - Archivo de bases de datos antiguas - No comment provided by engineer. - One-time invitation link Enlace de invitación de un solo uso @@ -4597,14 +5448,19 @@ Requiere activación de la VPN. No se usarán hosts .onion. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Sólo los propietarios del chat pueden cambiar las preferencias. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**. No comment provided by engineer. Only delete conversation - Sólo borrar la conversación + Eliminar sólo la conversación No comment provided by engineer. @@ -4622,6 +5478,16 @@ Requiere activación de la VPN. Sólo los propietarios del grupo pueden activar los mensajes de voz. No comment provided by engineer. + + Only sender and moderators see it + Solo el remitente y el moderador pueden verlo + No comment provided by engineer. + + + Only you and moderators see it + Solo tú y los moderadores podéis verlo + No comment provided by engineer. + Only you can add message reactions. Sólo tú puedes añadir reacciones a los mensajes. @@ -4675,13 +5541,18 @@ Requiere activación de la VPN. Open Abrir - No comment provided by engineer. + alert action Open Settings Abrir Configuración No comment provided by engineer. + + Open changes + Abrir cambios + No comment provided by engineer. + Open chat Abrir chat @@ -4692,36 +5563,45 @@ Requiere activación de la VPN. Abrir consola de Chat authentication reason + + Open conditions + Abrir condiciones + No comment provided by engineer. + Open group Grupo abierto No comment provided by engineer. + + Open link? + alert title + Open migration to another device Abrir menú migración a otro dispositivo authentication reason - - Open server settings - Abrir configuración del servidor - No comment provided by engineer. - - - Open user profiles - Abrir perfil de usuario - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protocolo y código abiertos: cualquiera puede usar los servidores. - No comment provided by engineer. - Opening app… Iniciando aplicación… No comment provided by engineer. + + Operator + Operador + No comment provided by engineer. + + + Operator server + Servidor del operador + alert title + + + Or import archive file + O importa desde un archivo + No comment provided by engineer. + Or paste archive link O pegar enlace del archivo @@ -4739,7 +5619,17 @@ Requiere activación de la VPN. Or show this code - O muestra este código QR + O muestra el código QR + No comment provided by engineer. + + + Or to share privately + O para compartir en privado + No comment provided by engineer. + + + Organize chats into lists + Organiza tus chats en listas No comment provided by engineer. @@ -4747,10 +5637,12 @@ Requiere activación de la VPN. Otro No comment provided by engineer. - - Other %@ servers - Otros servidores %@ - No comment provided by engineer. + + Other file errors: +%@ + Otro(s) error(es) de archivo. +%@ + alert message PING count @@ -4787,6 +5679,11 @@ Requiere activación de la VPN. ¡Código de acceso guardado! No comment provided by engineer. + + Password + Contraseña + No comment provided by engineer. + Password to show Contraseña para hacerlo visible @@ -4822,13 +5719,8 @@ Requiere activación de la VPN. Pendientes No comment provided by engineer. - - People can connect to you only via the links you share. - Las personas pueden conectarse contigo solo mediante los enlaces que compartes. - No comment provided by engineer. - - - Periodically + + Periodic Periódicamente No comment provided by engineer. @@ -4931,14 +5823,34 @@ Error: %@ Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Por favor, intenta desactivar y reactivar las notificaciones. + token info + + + Please wait for token activation to complete. + Por favor, espera a que el token de activación se complete. + token info + + + Please wait for token to be registered. + Por favor, espera a que el token se registre. + token info + Polish interface Interfaz en polaco No comment provided by engineer. + + Port + Puerto + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect - Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta + Posiblemente la huella del certificado en la dirección del servidor es incorrecta server test error @@ -4946,14 +5858,14 @@ Error: %@ Conserva el último borrador del mensaje con los datos adjuntos. No comment provided by engineer. - - Preset server - Servidor predefinido - No comment provided by engineer. - Preset server address - Dirección del servidor predefinida + Dirección predefinida del servidor + No comment provided by engineer. + + + Preset servers + Servidores predefinidos No comment provided by engineer. @@ -4971,16 +5883,36 @@ Error: %@ Seguridad y Privacidad No comment provided by engineer. + + Privacy for your customers. + Privacidad para tus clientes. + No comment provided by engineer. + + + Privacy policy and conditions of use. + Política de privacidad y condiciones de uso. + No comment provided by engineer. + Privacy redefined Privacidad redefinida 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. + No comment provided by engineer. + Private filenames Nombres de archivos privados No comment provided by engineer. + + Private media file names. + Nombres privados en archivos de media. + No comment provided by engineer. + Private message routing Enrutamiento privado de mensajes @@ -5008,7 +5940,7 @@ Error: %@ Profile and server connections - Datos del perfil y conexiones + Eliminar perfil y conexiones No comment provided by engineer. @@ -5021,16 +5953,6 @@ Error: %@ Forma de los perfiles No comment provided by engineer. - - Profile name - Nombre del perfil - No comment provided by engineer. - - - Profile name: - Nombre del perfil: - No comment provided by engineer. - Profile password Contraseña del perfil @@ -5044,7 +5966,7 @@ Error: %@ Profile update will be sent to your contacts. La actualización del perfil se enviará a tus contactos. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5988,11 @@ Error: %@ No se permiten reacciones a los mensajes. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No se permite informar de mensajes a los moderadores. + No comment provided by engineer. + Prohibit sending SimpleX links. No se permite enviar enlaces SimpleX. @@ -5098,7 +6025,7 @@ Error: %@ Protect app screen - Proteger la pantalla de la aplicación + Proteger la pantalla No comment provided by engineer. @@ -5133,9 +6060,14 @@ Actívalo en ajustes de *Servidores y Redes*. Servidores con proxy No comment provided by engineer. + + Proxy requires password + El proxy requiere contraseña + No comment provided by engineer. + Push notifications - Notificaciones automáticas + Notificaciones push No comment provided by engineer. @@ -5155,7 +6087,7 @@ Actívalo en ajustes de *Servidores y Redes*. Reachable chat toolbar - Barra de herramientas accesible + Barra de menú accesible No comment provided by engineer. @@ -5170,12 +6102,7 @@ Actívalo en ajustes de *Servidores y Redes*. Read more - Conoce más - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). + Saber más No comment provided by engineer. @@ -5183,16 +6110,16 @@ Actívalo en ajustes de *Servidores y Redes*. Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/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. - Conoce más en nuestro repositorio GitHub. - 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). @@ -5295,7 +6222,7 @@ Actívalo en ajustes de *Servidores y Redes*. Reconnect server to force message delivery. It uses additional traffic. - Reconectar el servidor para forzar la entrega de mensajes. Usa tráfico adicional. + Reconectar con el servidor para forzar la entrega de mensajes. Se usa tráfico adicional. No comment provided by engineer. @@ -5323,11 +6250,26 @@ Actívalo en ajustes de *Servidores y Redes*. Reducción del uso de batería No comment provided by engineer. + + Register + Registrar + No comment provided by engineer. + + + Register notification token? + ¿Registrar el token de notificaciones? + token info + + + Registered + Registrado + token status text + Reject Rechazar reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6296,11 @@ Actívalo en ajustes de *Servidores y Redes*. Eliminar No comment provided by engineer. + + Remove archive? + ¿Eliminar archivo? + No comment provided by engineer. + Remove image Eliminar imagen @@ -5419,6 +6366,56 @@ Actívalo en ajustes de *Servidores y Redes*. Responder chat item action + + Report + Informe + chat item action + + + Report content: only group moderators will see it. + Informar de contenido: sólo los moderadores del grupo lo verán. + report reason + + + Report member profile: only group moderators will see it. + Informar del perfil de un miembro: sólo los moderadores del grupo lo verán. + report reason + + + Report other: only group moderators will see it. + Informar de otros: sólo los moderadores del grupo lo verán. + report reason + + + Report reason? + ¿Motivo del informe? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Informar de spam: sólo los moderadores del grupo lo verán. + report reason + + + Report violation: only group moderators will see it. + Informar de violación: sólo los moderadores del grupo lo verán. + report reason + + + Report: %@ + Informe: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No se permite informar de mensajes a los moderadores. + No comment provided by engineer. + + + Reports + Informes + No comment provided by engineer. + Required Obligatorio @@ -5504,6 +6501,11 @@ Actívalo en ajustes de *Servidores y Redes*. Revelar chat item action + + Review conditions + Revisar condiciones + No comment provided by engineer. + Revoke Revocar @@ -5534,6 +6536,11 @@ Actívalo en ajustes de *Servidores y Redes*. Servidor SMP No comment provided by engineer. + + SOCKS proxy + Proxy SOCKS + No comment provided by engineer. + Safely receive files Recibe archivos de forma segura @@ -5547,17 +6554,18 @@ Actívalo en ajustes de *Servidores y Redes*. Save Guardar - chat item action + alert button +chat item action Save (and notify contacts) Guardar (y notificar contactos) - No comment provided by engineer. + alert button Save and notify contact Guardar y notificar contacto - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6582,16 @@ Actívalo en ajustes de *Servidores y Redes*. Guardar y actualizar perfil del grupo No comment provided by engineer. - - Save archive - Guardar archivo - No comment provided by engineer. - - - Save auto-accept settings - Guardar configuración de auto aceptar - No comment provided by engineer. - Save group profile Guardar perfil de grupo No comment provided by engineer. + + Save list + Guardar lista + No comment provided by engineer. + Save passphrase and open chat Guardar contraseña y abrir el chat @@ -5602,7 +6605,7 @@ Actívalo en ajustes de *Servidores y Redes*. Save preferences? ¿Guardar preferencias? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6620,18 @@ Actívalo en ajustes de *Servidores y Redes*. Save servers? ¿Guardar servidores? - No comment provided by engineer. - - - Save settings? - ¿Guardar configuración? - No comment provided by engineer. + alert title Save welcome message? ¿Guardar mensaje de bienvenida? No comment provided by engineer. + + Save your profile? + ¿Guardar tu perfil? + alert title + Saved Guardado @@ -5649,6 +6652,11 @@ Actívalo en ajustes de *Servidores y Redes*. Mensaje guardado message info title + + Saving %lld messages + Guardando %lld mensajes + No comment provided by engineer. + Scale Escala @@ -5681,7 +6689,7 @@ Actívalo en ajustes de *Servidores y Redes*. Scan server QR code - Escanear código QR del servidor + Escanear código QR No comment provided by engineer. @@ -5729,6 +6737,11 @@ Actívalo en ajustes de *Servidores y Redes*. Seleccionar chat item action + + Select chat profile + Selecciona perfil de chat + No comment provided by engineer. + Selected %lld Seleccionados %lld @@ -5776,7 +6789,7 @@ Actívalo en ajustes de *Servidores y Redes*. Send direct message to connect - Envia un mensaje para conectar + Envía un mensaje para conectar No comment provided by engineer. @@ -5819,9 +6832,9 @@ Actívalo en ajustes de *Servidores y Redes*. Enviar notificaciones No comment provided by engineer. - - Send notifications: - Enviar notificaciones: + + Send private reports + Envía informes privados No comment provided by engineer. @@ -5847,7 +6860,7 @@ Actívalo en ajustes de *Servidores y Redes*. Sender cancelled file transfer. El remitente ha cancelado la transferencia de archivos. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6957,16 @@ Actívalo en ajustes de *Servidores y Redes*. Mediante proxy No comment provided by engineer. + + Server + Servidor + No comment provided by engineer. + + + Server added to operator %@. + Servidor añadido al operador %@. + alert message + Server address Dirección del servidor @@ -5959,6 +6982,21 @@ Actívalo en ajustes de *Servidores y Redes*. La dirección del servidor es incompatible con la configuración de la red: %@. No comment provided by engineer. + + Server operator changed. + El operador del servidor ha cambiado. + alert title + + + Server operators + Operadores de servidores + No comment provided by engineer. + + + Server protocol changed. + El protocolo del servidor ha cambiado. + alert title + Server requires authorization to create queues, check password El servidor requiere autorización para crear colas, comprueba la contraseña @@ -5971,7 +7009,7 @@ Actívalo en ajustes de *Servidores y Redes*. Server test failed! - ¡Error en prueba del servidor! + ¡Prueba no superada! No comment provided by engineer. @@ -6001,7 +7039,7 @@ Actívalo en ajustes de *Servidores y Redes*. Servers statistics will be reset - this cannot be undone! - Las estadísticas de los servidores serán restablecidas. ¡No podrá deshacerse! + Las estadísticas de los servidores serán restablecidas. ¡No puede deshacerse! No comment provided by engineer. @@ -6014,6 +7052,11 @@ Actívalo en ajustes de *Servidores y Redes*. Establecer 1 día No comment provided by engineer. + + Set chat name… + Nombre para el chat… + No comment provided by engineer. + Set contact name… Escribe el nombre del contacto… @@ -6034,6 +7077,11 @@ Actívalo en ajustes de *Servidores y Redes*. Úsalo en lugar de la autenticación del sistema. No comment provided by engineer. + + Set message expiration in chats. + Establece el vencimiento para los mensajes en los chats. + No comment provided by engineer. + Set passcode Código autodestrucción @@ -6064,6 +7112,11 @@ Actívalo en ajustes de *Servidores y Redes*. Configuración No comment provided by engineer. + + Settings were changed. + La configuración ha sido modificada. + alert message + Shape profile images Dar forma a las imágenes de perfil @@ -6072,22 +7125,38 @@ Actívalo en ajustes de *Servidores y Redes*. Share Compartir - chat item action + alert action +chat item action Share 1-time link Compartir enlace de un uso No comment provided by engineer. + + Share 1-time link with a friend + Compartir enlace de un uso con un amigo + No comment provided by engineer. + + + Share SimpleX address on social media. + Comparte tu dirección SimpleX en redes sociales. + No comment provided by engineer. + Share address Compartir dirección No comment provided by engineer. + + Share address publicly + Campartir dirección públicamente + No comment provided by engineer. + Share address with contacts? ¿Compartir la dirección con los contactos? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7168,11 @@ Actívalo en ajustes de *Servidores y Redes*. Compartir enlace No comment provided by engineer. + + Share profile + Perfil a compartir + No comment provided by engineer. + Share this 1-time invite link Comparte este enlace de un solo uso @@ -6114,6 +7188,11 @@ Actívalo en ajustes de *Servidores y Redes*. Compartir con contactos No comment provided by engineer. + + Short link + Enlace corto + No comment provided by engineer. + Show QR code Mostrar código QR @@ -6169,6 +7248,11 @@ Actívalo en ajustes de *Servidores y Redes*. Dirección SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + Simplex Chat y Flux han acordado incluir en la aplicación servidores operados por Flux. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. La seguridad de SimpleX Chat ha sido auditada por Trail of Bits. @@ -6199,6 +7283,21 @@ Actívalo en ajustes de *Servidores y Redes*. Dirección SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + Compartir enlaces de un solo uso y direcciones SimpleX es seguro a través de cualquier medio. + No comment provided by engineer. + + + SimpleX address or 1-time link? + ¿Dirección SimpleX o enlace de un uso? + No comment provided by engineer. + + + SimpleX channel link + Enlace de canal SimpleX + simplex link type + SimpleX contact address Dirección de contacto SimpleX @@ -6219,8 +7318,8 @@ Actívalo en ajustes de *Servidores y Redes*. Enlaces SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Los enlaces SimpleX no se permiten en este grupo. No comment provided by engineer. @@ -6234,6 +7333,11 @@ Actívalo en ajustes de *Servidores y Redes*. Invitación SimpleX de un uso simplex link type + + SimpleX protocols reviewed by Trail of Bits. + Protocolos de SimpleX auditados por Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Modo incógnito simplificado @@ -6256,7 +7360,7 @@ Actívalo en ajustes de *Servidores y Redes*. Small groups (max 20) - Grupos pequeños (máx. 20) + Grupos pequeños (max. 20) No comment provided by engineer. @@ -6264,6 +7368,11 @@ Actívalo en ajustes de *Servidores y Redes*. Suave blur media + + Some app settings were not migrated. + Algunas configuraciones de la app no han sido migradas. + No comment provided by engineer. + Some file(s) were not exported: Algunos archivos no han sido exportados: @@ -6279,11 +7388,24 @@ Actívalo en ajustes de *Servidores y Redes*. Han ocurrido algunos errores no críticos durante la importación: No comment provided by engineer. + + Some servers failed the test: +%@ + Algunos servidores no han superado la prueba: +%@ + alert message + Somebody Alguien notification title + + Spam + Spam + blocking reason +report reason + Square, circle, or anything in between. Cuadrada, circular o cualquier forma intermedia. @@ -6329,14 +7451,9 @@ Actívalo en ajustes de *Servidores y Redes*. Parar SimpleX No comment provided by engineer. - - Stop chat to enable database actions - Para habilitar las acciones sobre la base de datos, debes parar SimpleX - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. - Para poder exportar, importar o eliminar la base de datos primero debes parar SimpleX. Mientras tanto no podrás recibir ni enviar mensajes. + Para exportar, importar o eliminar la base de datos debes parar SimpleX. Mientra tanto no podrás enviar ni recibir mensajes. No comment provided by engineer. @@ -6362,18 +7479,23 @@ Actívalo en ajustes de *Servidores y Redes*. Stop sharing Dejar de compartir - No comment provided by engineer. + alert action Stop sharing address? ¿Dejar de compartir la dirección? - No comment provided by engineer. + alert title Stopping chat Parando chat No comment provided by engineer. + + Storage + Almacenamiento + No comment provided by engineer. + Strong Fuerte @@ -6386,7 +7508,7 @@ Actívalo en ajustes de *Servidores y Redes*. Subscribed - Suscrito + Suscritas No comment provided by engineer. @@ -6404,6 +7526,16 @@ Actívalo en ajustes de *Servidores y Redes*. Soporte SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Intercambia audio y video durante la llamada. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Cambia el perfil de chat para invitaciones de un solo uso. + No comment provided by engineer. + System Sistema @@ -6424,6 +7556,11 @@ Actívalo en ajustes de *Servidores y Redes*. Timeout de la conexión TCP No comment provided by engineer. + + TCP port for messaging + Puerto TCP para mensajes + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7576,21 @@ Actívalo en ajustes de *Servidores y Redes*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Cola + No comment provided by engineer. + Take picture Tomar foto 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 button Pulsa el botón @@ -6482,13 +7629,18 @@ Actívalo en ajustes de *Servidores y Redes*. Temporary file error Error en archivo temporal - No comment provided by engineer. + file error alert title Test failed at step %@. - La prueba ha fallado en el paso %@. + Prueba no superada en el paso %@. server test failure + + Test notifications + Probar notificaciones + No comment provided by engineer. + Test server Probar servidor @@ -6501,8 +7653,8 @@ Actívalo en ajustes de *Servidores y Redes*. Tests failed! - ¡Pruebas fallidas! - No comment provided by engineer. + ¡Pruebas no superadas! + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7671,6 @@ Actívalo en ajustes de *Servidores y Redes*. ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - La primera plataforma sin identificadores de usuario: diseñada para la privacidad. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7683,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo. 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 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). @@ -6548,7 +7700,12 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The code you scanned is not a SimpleX link QR code. - El código QR escaneado no es un enlace SimpleX. + El código QR escaneado no es un enlace de SimpleX. + 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. No comment provided by engineer. @@ -6571,6 +7728,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 + No comment provided by engineer. + The hash of the previous message is different. El hash del mensaje anterior es diferente. @@ -6596,19 +7758,19 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Los mensajes serán marcados como moderados para todos los miembros. No comment provided by engineer. - - The next generation of private messaging - La nueva generación de mensajería privada - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. La base de datos antigua no se eliminó durante la migración, puede eliminarse. No comment provided by engineer. - - The profile is only shared with your contacts. - El perfil sólo se comparte con tus contactos. + + The same conditions will apply to operator **%@**. + Las mismas condiciones se aplicarán al operador **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + ¡Segundo operador predefinido! No comment provided by engineer. @@ -6623,12 +7785,22 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The servers for new connections of your current chat profile **%@**. - Lista de servidores para las conexiones nuevas de tu perfil actual **%@**. + Servidores para conexiones nuevas en tu perfil **%@**. + No comment provided by engineer. + + + The servers for new files of your current chat profile **%@**. + Servidores para enviar archivos en tu perfil **%@**. No comment provided by engineer. The text you pasted is not a SimpleX link. - El texto pegado no es un enlace SimpleX. + El texto pegado no es un enlace de SimpleX. + No comment provided by engineer. + + + The uploaded database archive will be permanently removed from the servers. + El archivo de bases de datos subido será eliminado permanentemente de los servidores. No comment provided by engineer. @@ -6636,6 +7808,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Temas No comment provided by engineer. + + These conditions will also apply for: **%@**. + Estas condiciones también se aplican para: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Esta configuración afecta a tu perfil actual **%@**. @@ -6656,6 +7833,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Esta acción es irreversible. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Podría tardar varios minutos. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No puede deshacerse! + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente. @@ -6701,11 +7883,21 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. ¡Este es tu propio enlace de un solo uso! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador. No comment provided by engineer. + + This message was deleted or not received yet. + El mensaje ha sido eliminado o aún no se ha recibido. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Esta configuración se aplica a los mensajes del perfil actual **%@**. @@ -6736,9 +7928,9 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Para hacer una conexión nueva No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + + 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. No comment provided by engineer. @@ -6758,6 +7950,26 @@ You will be prompted to complete authentication before this feature is enabled.< Se te pedirá que completes la autenticación antes de activar esta función. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Para proteger tu privacidad, SimpleX usa identificadores distintos para cada uno de tus contactos. + No comment provided by engineer. + + + To receive + Para recibir + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Para grabación de voz, por favor concede el permiso para usar el micrófono. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Para grabación de vídeo, por favor concede el permiso para usar la cámara. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Para grabar el mensaje de voz concede permiso para usar el micrófono. @@ -6768,11 +7980,21 @@ Se te pedirá que completes la autenticación antes de activar esta función.
Para hacer visible tu perfil oculto, introduce la contraseña en el campo de búsqueda del menú **Mis perfiles**. No comment provided by engineer. + + To send + Para enviar + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + Para usar los servidores de **%@**, debes aceptar las condiciones de uso. + 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. @@ -6788,6 +8010,11 @@ Se te pedirá que completes la autenticación antes de activar esta función.
Activa incógnito al conectar. No comment provided by engineer. + + Token status: %@. + Estado token: %@. + token status + Toolbar opacity Opacidad barra @@ -6855,7 +8082,7 @@ Se te pedirá que completes la autenticación antes de activar esta función.
Unblock member for all? - ¿Desbloquear miembro para todos? + ¿Desbloquear el miembro para todos? No comment provided by engineer. @@ -6863,6 +8090,11 @@ Se te pedirá que completes la autenticación antes de activar esta función.
¿Desbloquear miembro? No comment provided by engineer. + + Undelivered messages + Mensajes no entregados + No comment provided by engineer. + Unexpected migration state Estado de migración inesperado @@ -6911,7 +8143,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unknown servers! ¡Servidores desconocidos! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6921,7 +8153,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. 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. - A menos que tu contacto haya eliminado la conexión o el enlace haya sido usado, podría ser un error. Por favor, notifícalo. + A menos que tu contacto haya eliminado la conexión o el enlace se haya usado, podría ser un error. Por favor, notifícalo. Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión de red. No comment provided by engineer. @@ -6948,13 +8180,18 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Unmute Activar audio - swipe action + notification label action Unread No leído swipe action + + 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. @@ -6980,9 +8217,14 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión ¿Actualizar configuración? No comment provided by engineer. + + Updated conditions + Condiciones actualizadas + No comment provided by engineer. + Updating settings will re-connect the client to all servers. - Al actualizar la configuración el cliente se reconectará a todos los servidores. + Para actualizar la configuración el cliente se reconectará a todos los servidores. No comment provided by engineer. @@ -7020,16 +8262,36 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Subiendo archivo No comment provided by engineer. + + Use %@ + Usar %@ + No comment provided by engineer. + Use .onion hosts Usar hosts .onion No comment provided by engineer. + + Use SOCKS proxy + Usar proxy SOCKS + No comment provided by engineer. + Use SimpleX Chat servers? ¿Usar servidores SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + Se usa el puerto TCP %@ cuando no se ha especificado otro. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + Usar puerto TCP 443 solo en servidores predefinidos. + No comment provided by engineer. + Use chat Usar Chat @@ -7040,9 +8302,19 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar perfil actual No comment provided by engineer. + + Use for files + Uso para archivos + No comment provided by engineer. + + + Use for messages + Uso para mensajes + No comment provided by engineer. + Use for new connections - Usar para conexiones nuevas + Para conexiones nuevas No comment provided by engineer. @@ -7072,7 +8344,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use private routing with unknown servers. - Usar enrutamiento privado con servidores de retransmisión desconocidos. + Usar enrutamiento privado con servidores de mensaje desconocidos. No comment provided by engineer. @@ -7080,6 +8352,16 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar servidor No comment provided by engineer. + + Use servers + Usar servidores + No comment provided by engineer. + + + Use short links (BETA) + Usar enlaces cortos (BETA) + No comment provided by engineer. + Use the app while in the call. Usar la aplicación durante la llamada. @@ -7090,9 +8372,9 @@ 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. - - User profile - Perfil de usuario + + Use web port + Usar puerto web No comment provided by engineer. @@ -7100,6 +8382,11 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Selección de usuarios No comment provided by engineer. + + Username + Nombre de usuario + No comment provided by engineer. + Using SimpleX Chat servers. Usar servidores SimpleX Chat. @@ -7170,11 +8457,21 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Vídeos y archivos de hasta 1Gb No comment provided by engineer. + + View conditions + Ver condiciones + No comment provided by engineer. + View security code Mostrar código de seguridad No comment provided by engineer. + + View updated conditions + Ver condiciones actualizadas + No comment provided by engineer. + Visible history Historial visible @@ -7190,8 +8487,8 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Los mensajes de voz no están permitidos en este chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Los mensajes de voz no están permitidos en este grupo. No comment provided by engineer. @@ -7285,9 +8582,9 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Al iniciar llamadas de audio y vídeo. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Cuando alguien solicite conectarse podrás aceptar o rechazar la solicitud. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién. No comment provided by engineer. @@ -7333,7 +8630,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Sin Tor o VPN, tu dirección IP será visible para estos servidores XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8657,6 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Servidor XFTP No comment provided by engineer. - - You - - No comment provided by engineer. - You **must not** use the same database on two devices. **No debes** usar la misma base de datos en dos dispositivos. @@ -7387,7 +8679,12 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión You are already connected to %@. - Ya estás conectado a %@. + Ya estás conectado con %@. + No comment provided by engineer. + + + You are already connected with %@. + Ya estás conectado con %@. No comment provided by engineer. @@ -7439,7 +8736,7 @@ Repeat join request? You are not connected to these servers. Private routing is used to deliver messages to them. - No estás conectado a estos servidores. Para enviarles mensajes se usa el enrutamiento privado. + No tienes conexión directa a estos servidores. Los mensajes destinados a estos usan enrutamiento privado. No comment provided by engineer. @@ -7452,6 +8749,11 @@ Repeat join request? Puedes cambiar la posición de la barra desde el menú Apariencia. No comment provided by engineer. + + You can configure servers via settings. + Puedes configurar los servidores a través de su configuración. + No comment provided by engineer. + You can create it later Puedes crearla más tarde @@ -7492,6 +8794,11 @@ Repeat join request? Puedes enviar mensajes a %@ desde Contactos archivados. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + Puedes añadir un nombre a la conexión para recordar a quién corresponde. + No comment provided by engineer. + You can set lock screen notification preview via settings. Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración. @@ -7507,11 +8814,6 @@ Repeat join request? Puedes compartir esta dirección con tus contactos para que puedan conectar con **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Puedes compartir tu dirección como enlace o código QR para que cualquiera pueda conectarse contigo. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Puede iniciar Chat a través de la Configuración / Base de datos de la aplicación o reiniciando la aplicación @@ -7535,23 +8837,23 @@ Repeat join request? You can view invitation link again in connection details. Podrás ver el enlace de invitación en detalles de conexión. - No comment provided by engineer. + alert message You can't send messages! ¡No puedes enviar mensajes! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Tú controlas a través de qué servidor(es) **recibes** los mensajes. Tus contactos controlan a través de qué servidor(es) **envías** tus mensajes. - No comment provided by engineer. - You could not be verified; please try again. 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 via this address! ¡Ya has solicitado la conexión mediante esta dirección! @@ -7601,7 +8903,7 @@ Repeat connection request? You need to allow your contact to call to be able to call them. - Necesitas permitir que tus contacto llamen para poder llamarles. + Debes permitir que tus contacto te llamen para poder llamarles. No comment provided by engineer. @@ -7619,6 +8921,11 @@ Repeat connection request? Has enviado una invitación de grupo No comment provided by engineer. + + You should receive notifications. + Deberías recibir notificaciones. + token info + You will be connected to group when the group host's device is online, please wait or check later! Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o revisa más tarde. @@ -7654,6 +8961,11 @@ Repeat connection request? 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 de este chat. El historial del chat se conserva. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Dejarás de recibir mensajes de este grupo. El historial del chat se conservará. @@ -7674,31 +8986,16 @@ Repeat connection request? Estás usando un perfil incógnito en este grupo. Para evitar descubrir tu perfil principal no se permite invitar contactos No comment provided by engineer. - - Your %@ servers - Mis servidores %@ - No comment provided by engineer. - Your ICE servers Servidores ICE No comment provided by engineer. - - Your SMP servers - Servidores SMP - No comment provided by engineer. - Your SimpleX address Mi dirección SimpleX No comment provided by engineer. - - Your XFTP servers - Servidores XFTP - No comment provided by engineer. - Your calls Llamadas @@ -7714,11 +9011,21 @@ Repeat connection request? La base de datos no está cifrada - establece una contraseña para cifrarla. No comment provided by engineer. + + Your chat preferences + Tus preferencias de chat + alert title + Your chat profiles Mis perfiles No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Tu conexión ha sido trasladada a %@ pero ha ocurrido un error inesperado al redirigirte al perfil. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). El contacto ha enviado un archivo mayor al máximo admitido (%@). @@ -7734,6 +9041,11 @@ Repeat connection request? Tus contactos permanecerán conectados. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Tus credenciales podrían ser enviadas sin cifrar. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. La base de datos actual será ELIMINADA y SUSTITUIDA por la importada. @@ -7764,31 +9076,34 @@ Repeat connection request? El perfil **%@** será compartido. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. -Los servidores SimpleX no pueden ver tu perfil. + + Your profile is stored on your device and only shared with your contacts. + El perfil sólo se comparte con tus contactos. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Tu perfil, contactos y mensajes se almacenan en tu dispositivo. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. No comment provided by engineer. + + 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. + alert message + Your random profile Tu perfil aleatorio No comment provided by engineer. - - Your server - Tu servidor - No comment provided by engineer. - Your server address - Dirección de tu servidor + Dirección del servidor + No comment provided by engineer. + + + Your servers + Tus servidores No comment provided by engineer. @@ -7831,6 +9146,11 @@ Los servidores SimpleX no pueden ver tu perfil. llamada aceptada call status + + accepted invitation + invitación aceptada + chat list item title + admin administrador @@ -7866,6 +9186,11 @@ Los servidores SimpleX no pueden ver tu perfil. y %lld evento(s) más No comment provided by engineer. + + archived report + informes archivados + No comment provided by engineer. + attempts intentos @@ -7904,7 +9229,8 @@ Los servidores SimpleX no pueden ver tu perfil. blocked by admin bloqueado por administrador - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7988,7 +9314,7 @@ Los servidores SimpleX no pueden ver tu perfil. connecting - conectando + conectando... No comment provided by engineer. @@ -8019,7 +9345,7 @@ Los servidores SimpleX no pueden ver tu perfil. connecting… conectando… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9400,8 @@ Los servidores SimpleX no pueden ver tu perfil. default (%@) predeterminado (%@) - pref value + delete after time +pref value default (no) @@ -8098,7 +9425,7 @@ Los servidores SimpleX no pueden ver tu perfil. deleted group - grupo eliminado + ha eliminado el grupo rcv group event chat item @@ -8201,11 +9528,6 @@ Los servidores SimpleX no pueden ver tu perfil. error No comment provided by engineer. - - event happened - evento ocurrido - No comment provided by engineer. - expired expirados @@ -8376,20 +9698,20 @@ Los servidores SimpleX no pueden ver tu perfil. moderado por %@ marked deleted chat item preview text + + moderator + moderador + member role + months meses time unit - - mute - silenciar - No comment provided by engineer. - never nunca - No comment provided by engineer. + delete after time new message @@ -8420,8 +9742,8 @@ Los servidores SimpleX no pueden ver tu perfil. off desactivado enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9785,16 @@ Los servidores SimpleX no pueden ver tu perfil. p2p No comment provided by engineer. + + pending + pendiente + No comment provided by engineer. + + + pending approval + pendiente de aprobación + No comment provided by engineer. + quantum resistant e2e encryption cifrado e2e resistente a tecnología cuántica @@ -8478,6 +9810,11 @@ Los servidores SimpleX no pueden ver tu perfil. confirmación recibida… No comment provided by engineer. + + rejected + rechazado + No comment provided by engineer. + rejected call llamada rechazada @@ -8500,7 +9837,7 @@ Los servidores SimpleX no pueden ver tu perfil. removed profile picture - imagen de perfil eliminada + ha eliminado la imagen del perfil profile update event chat item @@ -8508,6 +9845,11 @@ Los servidores SimpleX no pueden ver tu perfil. te ha expulsado rcv group event chat item + + requested to connect + solicitado para conectar + chat list item title + saved guardado @@ -8564,7 +9906,7 @@ last received msg: %2$@ set new profile picture - nueva imagen de perfil + tiene nueva imagen del perfil profile update event chat item @@ -8607,11 +9949,6 @@ last received msg: %2$@ estado desconocido No comment provided by engineer. - - unmute - activar sonido - No comment provided by engineer. - unprotected con IP desprotegida @@ -8776,7 +10113,7 @@ last received msg: %2$@
- +
@@ -8813,7 +10150,7 @@ last received msg: %2$@
- +
@@ -8833,9 +10170,41 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + %d evento(s) nuevo(s) + notification body + + + From %d chat(s) + De %d chat(s) + notification body + + + From: %@ + De: %@ + notification body + + + New events + Eventos nuevos + notification + + + New messages + Mensajes nuevos + notification + + +
- +
@@ -8857,7 +10226,7 @@ last received msg: %2$@
- +
@@ -8912,7 +10281,7 @@ last received msg: %2$@ Database passphrase is different from saved in the keychain. - La contraseña de la base de datos es distinta a la almacenada en keychain. + La contraseña de la base de datos es diferente a la almacenada en keychain. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/es.xcloc/contents.json b/apps/ios/SimpleX Localizations/es.xcloc/contents.json index 340591e607..80cffac8d2 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/es.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "es", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 211e512a1e..a54666bb10 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (voidaan kopioida) @@ -124,6 +97,14 @@ %@ on vahvistettu No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded No comment provided by engineer. @@ -133,6 +114,10 @@ %@ haluaa muodostaa yhteyden! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -152,11 +137,31 @@ %d päivää time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d tuntia time interval + + %d messages not forwarded + alert title + %d min %d min @@ -172,6 +177,10 @@ %d sek time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d ohitettua viestiä @@ -237,11 +246,6 @@ %lld uutta käyttöliittymän kieltä No comment provided by engineer. - - %lld second(s) - %lld sekunti(a) - No comment provided by engineer. - %lld seconds %lld sekuntia @@ -292,11 +296,6 @@ %u viestit ohitettu. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) No comment provided by engineer. @@ -305,31 +304,21 @@ (this device v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille. + + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Yksityisempi**: tarkista uudet viestit 20 minuutin välein. Laitetunnus jaetaan SimpleX Chat -palvelimen kanssa, mutta ei sitä, kuinka monta yhteystietoa tai viestiä sinulla on. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta). No comment provided by engineer. @@ -342,11 +331,15 @@ **Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin. @@ -371,11 +364,6 @@ \*bold* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -406,11 +394,6 @@ - historian muokkaaminen. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec time to disappear @@ -423,7 +406,8 @@ 1 day 1 päivä - time interval + delete after time +time interval 1 hour @@ -438,12 +422,26 @@ 1 month 1 kuukausi - time interval + delete after time +time interval 1 week 1 viikko - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. 5 minutes @@ -460,11 +458,6 @@ 30 sekuntia No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -514,19 +507,13 @@ Keskeytä osoitteenvaihto? No comment provided by engineer. - - About SimpleX - Tietoja SimpleX:stä - No comment provided by engineer. - About SimpleX Chat Tietoja SimpleX Chatistä No comment provided by engineer. - - About SimpleX address - Tietoja SimpleX osoitteesta + + About operators No comment provided by engineer. @@ -537,8 +524,12 @@ Accept Hyväksy accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + No comment provided by engineer. Accept connection request? @@ -554,7 +545,11 @@ Accept incognito Hyväksy tuntematon accept contact request via notification - swipe action +swipe action + + + Accepted conditions + No comment provided by engineer. Acknowledged @@ -564,6 +559,10 @@ Acknowledgement errors No comment provided by engineer. + + Active + token status text + Active connections No comment provided by engineer. @@ -573,13 +572,12 @@ Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. No comment provided by engineer. - - Add contact + + Add friends No comment provided by engineer. - - Add preset servers - Lisää esiasetettuja palvelimia + + Add list No comment provided by engineer. @@ -597,16 +595,36 @@ Lisää palvelimia skannaamalla QR-koodeja. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Lisää toiseen laitteeseen No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message Lisää tervetuloviesti No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -629,6 +647,14 @@ Osoitteenmuutos keskeytetään. Käytetään vanhaa vastaanotto-osoitetta. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -647,6 +673,10 @@ Advanced settings No comment provided by engineer. + + All + No comment provided by engineer. + All app data is deleted. Kaikki sovelluksen tiedot poistetaan. @@ -657,13 +687,17 @@ Kaikki keskustelut ja viestit poistetaan - tätä ei voi kumota! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. Kaikki tiedot poistetaan, kun se syötetään. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. @@ -671,6 +705,10 @@ Kaikki ryhmän jäsenet pysyvät yhteydessä. 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! No comment provided by engineer. @@ -686,6 +724,14 @@ All profiles + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -759,6 +805,10 @@ Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia) No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. No comment provided by engineer. @@ -835,11 +885,20 @@ Luodaan tyhjä chat-profiili annetulla nimellä, ja sovellus avautuu normaalisti. No comment provided by engineer. + + Another reason + report reason + 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: %@ @@ -853,6 +912,10 @@ App encrypts new local files (except videos). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon Sovelluksen kuvake @@ -868,6 +931,10 @@ Sovelluksen pääsykoodi korvataan itsetuhoutuvalla pääsykoodilla. No comment provided by engineer. + + App session + No comment provided by engineer. + App version Sovellusversio @@ -891,6 +958,18 @@ Apply to No comment provided by engineer. + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? + No comment provided by engineer. + Archive and upload No comment provided by engineer. @@ -899,6 +978,18 @@ Archive contacts to chat later. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts No comment provided by engineer. @@ -967,6 +1058,10 @@ Hyväksy kuvat automaattisesti No comment provided by engineer. + + Auto-accept settings + alert title + Back Takaisin @@ -990,10 +1085,22 @@ Virheellinen viestin tarkiste No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + No comment provided by engineer. + Better messages Parempia viestejä @@ -1003,6 +1110,22 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1072,11 +1195,29 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + No comment provided by engineer. + + + Business chats + No comment provided by engineer. + + + Businesses + 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). 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! @@ -1120,7 +1261,8 @@ Cancel Peruuta - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1138,7 +1280,7 @@ Cannot receive file Tiedostoa ei voi vastaanottaa - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1153,6 +1295,14 @@ Muuta No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + authentication reason + Change database passphrase? Muutetaanko tietokannan tunnuslause? @@ -1197,11 +1347,18 @@ Change self-destruct passcode Vaihda itsetuhoutuva pääsykoodi authentication reason - set passcode view +set passcode view - - Chat archive - Chat-arkisto + + Chat + No comment provided by engineer. + + + Chat already exists + No comment provided by engineer. + + + Chat already exists! No comment provided by engineer. @@ -1259,19 +1416,44 @@ Chat-asetukset No comment provided by engineer. + + Chat preferences were changed. + alert message + + + Chat profile + Käyttäjäprofiili + No comment provided by engineer. + Chat theme No comment provided by engineer. + + 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. + Chats Keskustelut No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Tarkista palvelimen osoite ja yritä uudelleen. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1319,6 +1501,14 @@ Tyhjennä keskustelu? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? No comment provided by engineer. @@ -1336,6 +1526,10 @@ Color mode No comment provided by engineer. + + Community guidelines violation + report reason + Compare file Vertaa tiedostoa @@ -1350,13 +1544,41 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Määritä ICE-palvelimet No comment provided by engineer. - - Configured %@ servers + + Configure server operators No comment provided by engineer. @@ -1404,6 +1626,10 @@ Confirm upload No comment provided by engineer. + + Confirmed + token status text + Connect Yhdistä @@ -1505,6 +1731,10 @@ This is your own one-time link! Connection and servers status. No comment provided by engineer. + + Connection blocked + No comment provided by engineer. + Connection error Yhteysvirhe @@ -1515,6 +1745,15 @@ This is your own one-time link! Yhteysvirhe (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications No comment provided by engineer. @@ -1524,6 +1763,14 @@ This is your own one-time link! Yhteyspyyntö lähetetty! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1593,6 +1840,10 @@ This is your own one-time link! Kontaktit voivat merkitä viestit poistettaviksi; voit katsella niitä. No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue Jatka @@ -1616,6 +1867,10 @@ This is your own one-time link! Ydinversio: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -1625,6 +1880,10 @@ This is your own one-time link! Luo No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Luo SimpleX-osoite @@ -1634,11 +1893,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - Luo osoite, jolla ihmiset voivat ottaa sinuun yhteyttä. - No comment provided by engineer. - Create file Luo tiedosto @@ -1658,6 +1912,10 @@ This is your own one-time link! Luo linkki No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻 @@ -1665,6 +1923,7 @@ This is your own one-time link! Create profile + Luo profiilisi No comment provided by engineer. @@ -1694,11 +1953,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - Luotu %@ - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -1712,6 +1966,10 @@ This is your own one-time link! Nykyinen pääsykoodi No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Nykyinen tunnuslause… @@ -1731,6 +1989,10 @@ This is your own one-time link! Mukautettu aika No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -1859,8 +2121,8 @@ This is your own one-time link! Delete Poista - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -1894,14 +2156,12 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - Poista arkisto + + Delete chat No comment provided by engineer. - - Delete chat archive? - Poista keskusteluarkisto? + + Delete chat messages from your device. No comment provided by engineer. @@ -1914,6 +2174,10 @@ This is your own one-time link! Poista keskusteluprofiili? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Poista yhteys @@ -1987,6 +2251,10 @@ This is your own one-time link! Poista linkki? No comment provided by engineer. + + Delete list? + alert title + Delete member message? Poista jäsenviesti? @@ -2000,7 +2268,7 @@ This is your own one-time link! Delete messages Poista viestit - No comment provided by engineer. + alert button Delete messages after @@ -2017,6 +2285,10 @@ This is your own one-time link! Poista vanha tietokanta? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Poistetaanko odottava yhteys? @@ -2032,6 +2304,10 @@ This is your own one-time link! Poista jono server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. No comment provided by engineer. @@ -2063,6 +2339,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Toimitus @@ -2154,8 +2434,12 @@ This is your own one-time link! Yksityisviestit chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + + + Direct messages between members are prohibited. Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -2169,6 +2453,14 @@ This is your own one-time link! Poista SimpleX Lock käytöstä authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all Poista käytöstä kaikilta @@ -2193,8 +2485,8 @@ This is your own one-time link! Katoavat viestit ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Katoavat viestit ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -2248,6 +2540,14 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address Älä luo osoitetta @@ -2258,11 +2558,19 @@ This is your own one-time link! Älä salli No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again Älä näytä uudelleen No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat Alenna ja avaa keskustelu @@ -2270,7 +2578,8 @@ This is your own one-time link! Download - chat item action + alert button +chat item action Download errors @@ -2285,6 +2594,10 @@ This is your own one-time link! Lataa tiedosto server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2311,6 +2624,10 @@ This is your own one-time link! Kesto No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Muokkaa @@ -2331,6 +2648,10 @@ This is your own one-time link! Salli (pidä ohitukset) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Ota SimpleX Lock käyttöön @@ -2344,7 +2665,7 @@ This is your own one-time link! Enable automatic message deletion? Ota automaattinen viestien poisto käyttöön? - No comment provided by engineer. + alert title Enable camera access @@ -2463,6 +2784,10 @@ This is your own one-time link! Encryption re-negotiation failed. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Syötä pääsykoodi @@ -2524,26 +2849,33 @@ This is your own one-time link! Virhe osoitteenmuutoksen keskeytyksessä No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Virhe kontaktipyynnön hyväksymisessä No comment provided by engineer. - - Error accessing database file - Virhe tietokantatiedoston käyttämisessä - No comment provided by engineer. - Error adding member(s) Virhe lisättäessä jäseniä No comment provided by engineer. + + Error adding server + alert title + Error changing address Virhe osoitteenvaihdossa No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Virhe roolin vaihdossa @@ -2554,6 +2886,14 @@ This is your own one-time link! Virhe asetuksen muuttamisessa No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2573,6 +2913,10 @@ This is your own one-time link! Virhe ryhmälinkin luomisessa No comment provided by engineer. + + Error creating list + alert title + Error creating member contact No comment provided by engineer. @@ -2586,6 +2930,10 @@ This is your own one-time link! Virhe profiilin luomisessa! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file Virhe tiedoston salauksen purussa @@ -2664,9 +3012,12 @@ This is your own one-time link! Virhe ryhmään liittymisessä No comment provided by engineer. - - Error loading %@ servers - Virhe %@-palvelimien lataamisessa + + Error loading servers + alert title + + + Error migrating settings No comment provided by engineer. @@ -2676,7 +3027,7 @@ This is your own one-time link! Error receiving file Virhe tiedoston vastaanottamisessa - No comment provided by engineer. + alert title Error reconnecting server @@ -2686,25 +3037,32 @@ This is your own one-time link! Error reconnecting servers No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member Virhe poistettaessa jäsentä No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Virhe %@ palvelimien tallentamisessa - No comment provided by engineer. - Error saving ICE servers Virhe ICE-palvelimien tallentamisessa No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile Virhe ryhmäprofiilin tallentamisessa @@ -2720,6 +3078,10 @@ This is your own one-time link! Virhe tunnuslauseen tallentamisessa avainnippuun No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2762,16 +3124,24 @@ This is your own one-time link! Virhe keskustelun lopettamisessa No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Virhe profiilin vaihdossa! - No comment provided by engineer. + alertTitle Error synchronizing connection Virhe yhteyden synkronoinnissa No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link Virhe ryhmälinkin päivittämisessä @@ -2782,6 +3152,10 @@ This is your own one-time link! Virhe viestin päivityksessä No comment provided by engineer. + + Error updating server + alert title + Error updating settings Virhe asetusten päivittämisessä @@ -2808,8 +3182,9 @@ This is your own one-time link! Error: %@ Virhe: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2825,6 +3200,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Jopa kun ei käytössä keskustelussa. @@ -2839,6 +3218,10 @@ This is your own one-time link! Expand chat item action + + Expired + token status text + Export database Vie tietokanta @@ -2877,18 +3260,40 @@ This is your own one-time link! Nopea ja ei odotusta, kunnes lähettäjä on online-tilassa! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite Suosikki swipe action + + Favorites + No comment provided by engineer. + File error - No comment provided by engineer. + file error alert title + + + File errors: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -2940,8 +3345,8 @@ This is your own one-time link! Tiedostot ja media chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Tiedostot ja media ovat tässä ryhmässä kiellettyjä. No comment provided by engineer. @@ -3007,19 +3412,59 @@ This is your own one-time link! Ryhmän jäsen ei tue korjausta No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + servers error + For console Konsoliin No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3028,6 +3473,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3069,11 +3518,6 @@ Error: %2$@ Koko nimi (valinnainen) No comment provided by engineer. - - Full name: - Koko nimi: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -3093,6 +3537,10 @@ Error: %2$@ GIFit ja tarrat No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! message preview @@ -3154,40 +3602,6 @@ Error: %2$@ Ryhmälinkit No comment provided by engineer. - - Group members can add message reactions. - Ryhmän jäsenet voivat lisätä viestireaktioita. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia) - No comment provided by engineer. - - - Group members can send SimpleX links. - No comment provided by engineer. - - - Group members can send direct messages. - Ryhmän jäsenet voivat lähettää suoraviestejä. - No comment provided by engineer. - - - Group members can send disappearing messages. - Ryhmän jäsenet voivat lähettää katoavia viestejä. - No comment provided by engineer. - - - Group members can send files and media. - Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa. - No comment provided by engineer. - - - Group members can send voice messages. - Ryhmän jäsenet voivat lähettää ääniviestejä. - No comment provided by engineer. - Group message: Ryhmäviesti: @@ -3228,11 +3642,19 @@ Error: %2$@ Ryhmä poistetaan sinulta - tätä ei voi perua! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Apua No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Piilotettu @@ -3282,10 +3704,17 @@ Error: %2$@ Miten SimpleX toimii No comment provided by engineer. + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + No comment provided by engineer. + How it works - Kuinka se toimii - No comment provided by engineer. + alert button How to @@ -3311,6 +3740,10 @@ Error: %2$@ ICE-palvelimet (yksi per rivi) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki. @@ -3351,8 +3784,8 @@ Error: %2$@ Heti No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immuuni roskapostille ja väärinkäytöksille No comment provided by engineer. @@ -3383,6 +3816,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -3410,6 +3848,14 @@ Error: %2$@ In-call sounds No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Incognito @@ -3478,6 +3924,11 @@ Error: %2$@ Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Heti + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3485,11 +3936,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Heti - No comment provided by engineer. - Interface Käyttöliittymä @@ -3499,6 +3945,26 @@ Error: %2$@ Interface colors No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code No comment provided by engineer. @@ -3531,7 +3997,7 @@ Error: %2$@ Invalid server address! Virheellinen palvelinosoite! - No comment provided by engineer. + alert title Invalid status @@ -3553,6 +4019,10 @@ Error: %2$@ Kutsu jäseniä No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Kutsu ryhmään @@ -3568,8 +4038,8 @@ Error: %2$@ Viestien peruuttamaton poisto on kielletty tässä keskustelussa. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Viestien peruuttamaton poisto on kielletty tässä ryhmässä. No comment provided by engineer. @@ -3652,7 +4122,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3664,7 +4134,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3701,6 +4171,14 @@ This is your link for group %@! Poistu swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Poistu ryhmästä @@ -3738,6 +4216,18 @@ This is your link for group %@! Linked desktops No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Live-viesti! @@ -3748,11 +4238,6 @@ This is your link for group %@! Live-viestit No comment provided by engineer. - - Local - Paikallinen - No comment provided by engineer. - Local name Paikallinen nimi @@ -3773,11 +4258,6 @@ This is your link for group %@! Lukitustila No comment provided by engineer. - - Make a private connection - Luo yksityinen yhteys - No comment provided by engineer. - Make one message disappear Hävitä yksi viesti @@ -3788,21 +4268,11 @@ This is your link for group %@! Tee profiilista yksityinen! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Varmista, että %@-palvelinosoitteet ovat oikeassa muodossa, että ne on erotettu toisistaan riveittäin ja että ne eivät ole päällekkäisiä (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Varmista, että WebRTC ICE -palvelinosoitteet ovat oikeassa muodossa, rivieroteltuina ja että ne eivät ole päällekkäisiä. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Monet ihmiset kysyivät: *Jos SimpleX:llä ei ole käyttäjätunnuksia, miten se voi toimittaa viestejä?* - No comment provided by engineer. - Mark deleted for everyone Merkitse poistetuksi kaikilta @@ -3845,6 +4315,14 @@ This is your link for group %@! Member inactive item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Jäsenen rooli muuttuu muotoon "%@". Kaikille ryhmän jäsenille ilmoitetaan asiasta. @@ -3855,11 +4333,57 @@ This is your link for group %@! Jäsenen rooli muutetaan muotoon "%@". Jäsen saa uuden kutsun. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Jäsen poistetaan ryhmästä - tätä ei voi perua! No comment provided by engineer. + + Members can add message reactions. + Ryhmän jäsenet voivat lisätä viestireaktioita. + 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) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + Ryhmän jäsenet voivat lähettää suoraviestejä. + No comment provided by engineer. + + + Members can send disappearing messages. + Ryhmän jäsenet voivat lähettää katoavia viestejä. + No comment provided by engineer. + + + Members can send files and media. + Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa. + No comment provided by engineer. + + + Members can send voice messages. + Ryhmän jäsenet voivat lähettää ääniviestejä. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus No comment provided by engineer. @@ -3905,8 +4429,8 @@ This is your link for group %@! Viestireaktiot ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Viestireaktiot ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -3918,6 +4442,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3953,6 +4481,10 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received No comment provided by engineer. @@ -3961,6 +4493,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4017,9 +4553,9 @@ This is your link for group %@! Siirto on valmis No comment provided by engineer. - - Migrations: %@ - Siirrot: %@ + + Migrations: + Siirrot: No comment provided by engineer. @@ -4037,6 +4573,10 @@ This is your link for group %@! Moderoitu klo: %@ copied message info + + More + swipe action + More improvements are coming soon! Lisää parannuksia on tulossa pian! @@ -4046,6 +4586,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Todennäköisesti tämä yhteys on poistettu. @@ -4059,7 +4603,11 @@ This is your link for group %@! Mute Mykistä - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4080,6 +4628,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4088,6 +4640,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Verkkoasetukset @@ -4098,11 +4654,23 @@ This is your link for group %@! Verkon tila No comment provided by engineer. + + New + token status text + New Passcode Uusi pääsykoodi No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + No comment provided by engineer. + New chat No comment provided by engineer. @@ -4121,11 +4689,6 @@ This is your link for group %@! Uusi kontakti: notification - - New database archive - Uusi tietokanta-arkisto - No comment provided by engineer. - New desktop app! No comment provided by engineer. @@ -4135,6 +4698,10 @@ This is your link for group %@! Uusi näyttönimi No comment provided by engineer. + + New events + notification + New in %@ Uutta %@ @@ -4159,6 +4726,10 @@ This is your link for group %@! Uusi tunnuslause… No comment provided by engineer. + + New server + No comment provided by engineer. + No Ei @@ -4169,6 +4740,18 @@ This is your link for group %@! Ei sovelluksen salasanaa Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Kontakteja ei ole valittu @@ -4212,28 +4795,90 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection No comment provided by engineer. + + No permission to record speech + No comment provided by engineer. + + + No permission to record video + No comment provided by engineer. + No permission to record voice message Ei lupaa ääniviestin tallentamiseen No comment provided by engineer. + + No push server + Paikallinen + No comment provided by engineer. + No received or sent files Ei vastaanotettuja tai lähetettyjä tiedostoja No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. + No comment provided by engineer. + Not compatible! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Ilmoitukset @@ -4244,6 +4889,18 @@ This is your link for group %@! Ilmoitukset on poistettu käytöstä! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4265,18 +4922,13 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database Vanha tietokanta No comment provided by engineer. - - Old database archive - Vanha tietokanta-arkisto - No comment provided by engineer. - One-time invitation link Kertakutsulinkki @@ -4301,8 +4953,12 @@ Edellyttää VPN:n sallimista. Onion-isäntiä ei käytetä. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**. No comment provided by engineer. @@ -4325,6 +4981,14 @@ Edellyttää VPN:n sallimista. Vain ryhmän omistajat voivat ottaa ääniviestit käyttöön. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Vain sinä voit lisätä viestireaktioita. @@ -4377,13 +5041,17 @@ Edellyttää VPN:n sallimista. Open - No comment provided by engineer. + alert action Open Settings Avaa Asetukset No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Avaa keskustelu @@ -4394,32 +5062,38 @@ Edellyttää VPN:n sallimista. Avaa keskustelukonsoli authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. + + Open link? + alert title + Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Avaa käyttäjäprofiilit - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -4436,13 +5110,22 @@ Edellyttää VPN:n sallimista. Or show this code No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + alert message PING count @@ -4479,6 +5162,10 @@ Edellyttää VPN:n sallimista. Pääsykoodi asetettu! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Salasana näytettäväksi @@ -4509,13 +5196,8 @@ Edellyttää VPN:n sallimista. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - Ihmiset voivat ottaa sinuun yhteyttä vain jakamiesi linkkien kautta. - No comment provided by engineer. - - - Periodically + + Periodic Ajoittain No comment provided by engineer. @@ -4610,11 +5292,27 @@ Error: %@ Säilytä tunnuslause turvallisesti, ET voi muuttaa sitä, jos kadotat sen. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Puolalainen käyttöliittymä No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen @@ -4625,16 +5323,15 @@ Error: %@ Säilytä viimeinen viestiluonnos liitteineen. No comment provided by engineer. - - Preset server - Esiasetettu palvelin - No comment provided by engineer. - Preset server address Esiasetettu palvelimen osoite No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Esikatselu @@ -4649,16 +5346,32 @@ Error: %@ Yksityisyys ja turvallisuus No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Yksityisyys uudelleen määritettynä No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Yksityiset tiedostonimet No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing No comment provided by engineer. @@ -4693,14 +5406,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password Profiilin salasana @@ -4713,7 +5418,7 @@ Error: %@ Profile update will be sent to your contacts. Profiilipäivitys lähetetään kontakteillesi. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4735,6 +5440,10 @@ Error: %@ Estä viestireaktiot. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. No comment provided by engineer. @@ -4796,6 +5505,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-ilmoitukset @@ -4833,25 +5546,20 @@ Enable in *Network & servers* settings. Lue lisää No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/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. - Lue lisää GitHub-tietovarastostamme. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme). @@ -4971,11 +5679,23 @@ Enable in *Network & servers* settings. Pienempi akun käyttö No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Hylkää reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5002,6 +5722,10 @@ Enable in *Network & servers* settings. Poista No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5061,6 +5785,46 @@ Enable in *Network & servers* settings. Vastaa chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Pakollinen @@ -5140,6 +5904,10 @@ Enable in *Network & servers* settings. Paljasta chat item action + + Review conditions + No comment provided by engineer. + Revoke Peruuta @@ -5169,6 +5937,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5180,17 +5952,18 @@ Enable in *Network & servers* settings. Save Tallenna - chat item action + alert button +chat item action Save (and notify contacts) Tallenna (ja ilmoita kontakteille) - No comment provided by engineer. + alert button Save and notify contact Tallenna ja ilmoita kontaktille - No comment provided by engineer. + alert button Save and notify group members @@ -5206,21 +5979,15 @@ Enable in *Network & servers* settings. Tallenna ja päivitä ryhmäprofiili No comment provided by engineer. - - Save archive - Tallenna arkisto - No comment provided by engineer. - - - Save auto-accept settings - Tallenna automaattisen hyväksynnän asetukset - No comment provided by engineer. - Save group profile Tallenna ryhmäprofiili No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Tallenna tunnuslause ja avaa keskustelu @@ -5234,7 +6001,7 @@ Enable in *Network & servers* settings. Save preferences? Tallenna asetukset? - No comment provided by engineer. + alert title Save profile password @@ -5249,18 +6016,17 @@ Enable in *Network & servers* settings. Save servers? Tallenna palvelimet? - No comment provided by engineer. - - - Save settings? - Tallenna asetukset? - No comment provided by engineer. + alert title Save welcome message? Tallenna tervetuloviesti? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5278,6 +6044,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5351,6 +6121,10 @@ Enable in *Network & servers* settings. Valitse chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5434,9 +6208,8 @@ Enable in *Network & servers* settings. Lähetys ilmoitukset No comment provided by engineer. - - Send notifications: - Lähetys ilmoitukset: + + Send private reports No comment provided by engineer. @@ -5461,7 +6234,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Lähettäjä peruutti tiedoston siirron. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5553,6 +6326,14 @@ Enable in *Network & servers* settings. Sent via proxy No comment provided by engineer. + + Server + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5565,6 +6346,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana @@ -5614,6 +6407,10 @@ Enable in *Network & servers* settings. Aseta 1 päivä No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Aseta kontaktin nimi… @@ -5633,6 +6430,10 @@ Enable in *Network & servers* settings. Aseta se järjestelmän todennuksen sijaan. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Aseta pääsykoodi @@ -5662,6 +6463,10 @@ Enable in *Network & servers* settings. Asetukset No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5669,22 +6474,35 @@ Enable in *Network & servers* settings. Share Jaa - chat item action + alert action +chat item action Share 1-time link Jaa kertakäyttölinkki No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Jaa osoite No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Jaa osoite kontakteille? - No comment provided by engineer. + alert title Share from other apps. @@ -5695,6 +6513,10 @@ Enable in *Network & servers* settings. Jaa linkki No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -5708,6 +6530,10 @@ Enable in *Network & servers* settings. Jaa kontaktien kanssa No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -5758,6 +6584,10 @@ Enable in *Network & servers* settings. SimpleX-osoite No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Trail of Bits on tarkastanut SimpleX Chatin tietoturvan. @@ -5788,6 +6618,18 @@ Enable in *Network & servers* settings. SimpleX-osoite No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX-yhteystiedot @@ -5808,8 +6650,8 @@ Enable in *Network & servers* settings. SimpleX-linkit chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -5821,6 +6663,10 @@ Enable in *Network & servers* settings. SimpleX-kertakutsu simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode No comment provided by engineer. @@ -5848,6 +6694,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -5861,11 +6711,21 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Joku notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. No comment provided by engineer. @@ -5906,11 +6766,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Pysäytä keskustelu tietokantatoimien mahdollistamiseksi - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Pysäytä keskustelut viedäksesi, tuodaksesi tai poistaaksesi keskustelujen tietokannan. Et voi vastaanottaa ja lähettää viestejä, kun keskustelut on pysäytetty. @@ -5939,17 +6794,21 @@ Enable in *Network & servers* settings. Stop sharing Lopeta jakaminen - No comment provided by engineer. + alert action Stop sharing address? Lopeta osoitteen jakaminen? - No comment provided by engineer. + alert title Stopping chat No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong blur media @@ -5976,6 +6835,14 @@ Enable in *Network & servers* settings. SimpleX Chat tuki No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Järjestelmä @@ -5995,6 +6862,10 @@ Enable in *Network & servers* settings. TCP-yhteyden aikakatkaisu No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6010,11 +6881,19 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Ota kuva No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Napauta painiketta @@ -6049,13 +6928,17 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + file error alert title Test failed at step %@. Testi epäonnistui vaiheessa %@. server test failure + + Test notifications + No comment provided by engineer. + Test server Testipalvelin @@ -6069,7 +6952,7 @@ Enable in *Network & servers* settings. Tests failed! Testit epäonnistuivat! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6086,11 +6969,6 @@ Enable in *Network & servers* settings. Kiitokset käyttäjille – osallistu Weblaten kautta! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6103,6 +6981,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6116,6 +6998,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Hyväksymäsi yhteys peruuntuu! @@ -6136,6 +7022,11 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. Edellisen viestin tarkiste on erilainen. @@ -6159,19 +7050,17 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - Seuraavan sukupolven yksityisviestit - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. No comment provided by engineer. - - The profile is only shared with your contacts. - Profiili jaetaan vain kontaktiesi kanssa. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6189,14 +7078,26 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Palvelimet nykyisen keskusteluprofiilisi uusille yhteyksille **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Nämä asetukset koskevat nykyistä profiiliasi **%@**. @@ -6217,6 +7118,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Tätä toimintoa ei voi kumota - valittua aikaisemmin lähetetyt ja vastaanotetut viestit poistetaan. Tämä voi kestää useita minuutteja. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Tätä toimintoa ei voi kumota - profiilisi, kontaktisi, viestisi ja tiedostosi poistuvat peruuttamattomasti. @@ -6256,10 +7161,18 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.This is your own one-time link! 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. + No comment provided by engineer. + 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. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Tämä asetus koskee nykyisen keskusteluprofiilisi viestejä *%@**. @@ -6288,9 +7201,8 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Uuden yhteyden luominen No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6309,6 +7221,23 @@ You will be prompted to complete authentication before this feature is enabled.< Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus otetaan käyttöön. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Jos haluat nauhoittaa ääniviestin, anna lupa käyttää mikrofonia. @@ -6319,11 +7248,19 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Voit paljastaa piilotetun profiilisi syöttämällä koko salasanan hakukenttään **Keskusteluprofiilisi** -sivulla. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Keskustelujen-tietokanta on siirrettävä välittömien push-ilmoitusten tukemiseksi. No comment provided by engineer. + + To 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. Voit tarkistaa päästä päähän -salauksen kontaktisi kanssa vertaamalla (tai skannaamalla) laitteidenne koodia. @@ -6337,6 +7274,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Toggle incognito when connecting. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity No comment provided by engineer. @@ -6403,6 +7344,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Odottamaton siirtotila @@ -6450,7 +7395,7 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6485,13 +7430,17 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Unmute Poista mykistys - swipe action + notification label action Unread Lukematon swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -6515,6 +7464,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Update settings? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Asetusten päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin. @@ -6550,16 +7503,32 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Käytä .onion-isäntiä No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Käytä SimpleX Chat palvelimia? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Käytä chattia @@ -6570,6 +7539,14 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä nykyistä profiilia No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Käytä uusiin yhteyksiin @@ -6606,6 +7583,14 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä palvelinta No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6614,15 +7599,18 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use the app with one hand. No comment provided by engineer. - - User profile - Käyttäjäprofiili + + Use web port No comment provided by engineer. User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Käyttää SimpleX Chat -palvelimia. @@ -6687,11 +7675,19 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Videot ja tiedostot 1 Gt asti No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Näytä turvakoodi No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -6706,8 +7702,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Ääniviestit ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Ääniviestit ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -6794,9 +7790,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Kun ihmiset pyytävät yhteyden muodostamista, voit hyväksyä tai hylätä sen. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -6835,7 +7830,7 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -6859,11 +7854,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja XFTP server No comment provided by engineer. - - You - Sinä - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. @@ -6888,6 +7878,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Olet jo muodostanut yhteyden %@:n kanssa. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. @@ -6940,6 +7934,10 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure servers via settings. + No comment provided by engineer. + You can create it later Voit luoda sen myöhemmin @@ -6977,6 +7975,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Voit määrittää lukitusnäytön ilmoituksen esikatselun asetuksista. @@ -6992,11 +7994,6 @@ Repeat join request? Voit jakaa tämän osoitteen kontaktiesi kanssa, jotta ne voivat muodostaa yhteyden **%@** kanssa. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Voit jakaa osoitteesi linkkinä tai QR-koodina - kuka tahansa voi muodostaa yhteyden sinuun. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Voit aloittaa keskustelun sovelluksen Asetukset / Tietokanta kautta tai käynnistämällä sovelluksen uudelleen @@ -7018,23 +8015,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! Et voi lähettää viestejä! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Sinä hallitset, minkä palvelim(i)en kautta **viestit vastaanotetaan**, kontaktisi - palvelimet, joita käytät viestien lähettämiseen niille. - No comment provided by engineer. - You could not be verified; please try again. Sinua ei voitu todentaa; yritä uudelleen. No comment provided by engineer. + + You decide who can connect. + Kimin bağlanabileceğine siz karar verirsiniz. + No comment provided by engineer. + You have already requested connection via this address! No comment provided by engineer. @@ -7096,6 +8093,10 @@ Repeat connection request? Lähetit ryhmäkutsun No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! Sinut yhdistetään ryhmään, kun ryhmän isännän laite on online-tilassa, odota tai tarkista myöhemmin! @@ -7129,6 +8130,10 @@ Repeat connection request? Saat edelleen puheluita ja ilmoituksia mykistetyiltä profiileilta, kun ne ovat aktiivisia. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Et enää saa viestejä tästä ryhmästä. Keskusteluhistoria säilytetään. @@ -7149,31 +8154,16 @@ Repeat connection request? Käytät tässä ryhmässä incognito-profiilia. Kontaktien kutsuminen ei ole sallittua, jotta pääprofiilisi ei tule jaetuksi No comment provided by engineer. - - Your %@ servers - %@-palvelimesi - No comment provided by engineer. - Your ICE servers ICE-palvelimesi No comment provided by engineer. - - Your SMP servers - SMP-palvelimesi - No comment provided by engineer. - Your SimpleX address SimpleX-osoitteesi No comment provided by engineer. - - Your XFTP servers - XFTP-palvelimesi - No comment provided by engineer. - Your calls Puhelusi @@ -7189,11 +8179,19 @@ Repeat connection request? Keskustelut-tietokantasi ei ole salattu - aseta tunnuslause sen salaamiseksi. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Keskusteluprofiilisi No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Yhteyshenkilösi lähetti tiedoston, joka on suurempi kuin tällä hetkellä tuettu enimmäiskoko (%@). @@ -7209,6 +8207,10 @@ Repeat connection request? Kontaktisi pysyvät yhdistettyinä. 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. Nykyinen keskustelut-tietokantasi poistetaan ja korvataan tuodulla tietokannalla. @@ -7238,33 +8240,34 @@ Repeat connection request? Profiilisi **%@** jaetaan. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. -SimpleX-palvelimet eivät näe profiiliasi. + + Your profile is stored on your device and only shared with your contacts. + Profiili jaetaan vain kontaktiesi kanssa. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + alert message + Your random profile Satunnainen profiilisi No comment provided by engineer. - - Your server - Palvelimesi - No comment provided by engineer. - Your server address Palvelimesi osoite No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Asetuksesi @@ -7305,6 +8308,10 @@ SimpleX-palvelimet eivät näe profiiliasi. hyväksytty puhelu call status + + accepted invitation + chat list item title + admin ylläpitäjä @@ -7337,6 +8344,10 @@ SimpleX-palvelimet eivät näe profiiliasi. and %lld other events No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts No comment provided by engineer. @@ -7370,7 +8381,8 @@ SimpleX-palvelimet eivät näe profiiliasi. blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7483,7 +8495,7 @@ SimpleX-palvelimet eivät näe profiiliasi. connecting… yhdistää… - chat list item title + No comment provided by engineer. connection established @@ -7536,7 +8548,8 @@ SimpleX-palvelimet eivät näe profiiliasi. default (%@) oletusarvo (%@) - pref value + delete after time +pref value default (no) @@ -7661,11 +8674,6 @@ SimpleX-palvelimet eivät näe profiiliasi. virhe No comment provided by engineer. - - event happened - tapahtuma tapahtui - No comment provided by engineer. - expired No comment provided by engineer. @@ -7830,19 +8838,19 @@ SimpleX-palvelimet eivät näe profiiliasi. %@ moderoi marked deleted chat item preview text + + moderator + member role + months kuukautta time unit - - mute - No comment provided by engineer. - never ei koskaan - No comment provided by engineer. + delete after time new message @@ -7873,8 +8881,8 @@ SimpleX-palvelimet eivät näe profiiliasi. off pois enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -7913,6 +8921,14 @@ SimpleX-palvelimet eivät näe profiiliasi. vertais No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -7927,6 +8943,10 @@ SimpleX-palvelimet eivät näe profiiliasi. vahvistus saatu… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call hylätty puhelu @@ -7955,6 +8975,10 @@ SimpleX-palvelimet eivät näe profiiliasi. poisti sinut rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. @@ -8041,10 +9065,6 @@ last received msg: %2$@ unknown status No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected No comment provided by engineer. @@ -8201,7 +9221,7 @@ last received msg: %2$@
- +
@@ -8237,7 +9257,7 @@ last received msg: %2$@
- +
@@ -8257,9 +9277,36 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
@@ -8278,7 +9325,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/contents.json b/apps/ios/SimpleX Localizations/fi.xcloc/contents.json index 78ce40cec5..11f7a4861c 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/fi.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "fi", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 c05098980e..59bde0650e 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (peut être copié) @@ -127,6 +100,16 @@ %@ est vérifié·e No comment provided by engineer. + + %@ server + Serveur %@ + No comment provided by engineer. + + + %@ servers + Serveurs %@ + No comment provided by engineer. + %@ uploaded %@ envoyé @@ -137,6 +120,11 @@ %@ veut se connecter ! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ et %lld membres @@ -157,11 +145,36 @@ %d jours time interval + + %d file(s) are still being downloaded. + %d fichier(s) en cours de téléchargement. + forward confirmation reason + + + %d file(s) failed to download. + Le téléchargement de %d fichier(s) a échoué. + forward confirmation reason + + + %d file(s) were deleted. + Le(s) fichier(s) %d a(ont) été supprimé(s). + forward confirmation reason + + + %d file(s) were not downloaded. + Le(s) fichier(s) %d n'a (n'ont) pas été téléchargé(s). + forward confirmation reason + %d hours %d heures time interval + + %d messages not forwarded + %d messages non transférés + alert title + %d min %d min @@ -177,6 +190,11 @@ %d sec time interval + + %d seconds(s) + %d seconde(s) + delete after time + %d skipped message(s) %d message·s sauté·s @@ -247,11 +265,6 @@ %lld nouvelles langues d'interface No comment provided by engineer. - - %lld second(s) - %lld seconde·s - No comment provided by engineer. - %lld seconds %lld secondes @@ -302,11 +315,6 @@ %u messages sautés. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nouveau) @@ -317,19 +325,9 @@ (cet appareil v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Ajouter un contact** : pour créer un nouveau lien d'invitation ou vous connecter via un lien que vous avez reçu. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Ajouter un nouveau contact** : pour créer un lien ou code QR unique pour votre contact. + + **Create 1-time link**: to create and share a new invitation link. + **Ajouter un contact** : pour créer un nouveau lien d'invitation. No comment provided by engineer. @@ -337,13 +335,13 @@ **Créer un groupe** : pour créer un nouveau groupe. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Vie privée** : vérification de nouveaux messages toute les 20 minutes. Le token de l'appareil est partagé avec le serveur SimpleX, mais pas le nombre de messages ou de contacts. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app). No comment provided by engineer. @@ -357,11 +355,16 @@ **Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + **Scanner / Coller** : pour vous connecter via un lien que vous avez reçu. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain. @@ -387,11 +390,6 @@ \*gras* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - l'historique de modification. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -446,7 +439,8 @@ 1 day 1 jour - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 mois - time interval + delete after time +time interval 1 week 1 semaine - time interval + delete after time +time interval + + + 1 year + 1 an + delete after time + + + 1-time link + Lien unique + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Le lien unique peut être utilisé *avec un seul contact* - partagez le en personne ou via n'importe quelle messagerie. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 secondes No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,19 +543,14 @@ Abandonner le changement d'adresse ? No comment provided by engineer. - - About SimpleX - À propos de SimpleX - No comment provided by engineer. - About SimpleX Chat À propos de SimpleX Chat No comment provided by engineer. - - About SimpleX address - À propos de l'adresse SimpleX + + About operators + À propos des opérateurs No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Accepter accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Accepter les conditions + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Accepter en incognito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Conditions acceptées + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Erreur d'accusé de réception No comment provided by engineer. + + Active + Actif + token status text + Active connections Connections actives @@ -600,14 +616,14 @@ 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 contact - Ajouter le contact + + Add friends + Ajouter des amis No comment provided by engineer. - - Add preset servers - Ajouter des serveurs prédéfinis + + Add list + Ajouter une liste No comment provided by engineer. @@ -625,16 +641,41 @@ Ajoutez des serveurs en scannant des codes QR. No comment provided by engineer. + + Add team members + Ajouter des membres à l'équipe + No comment provided by engineer. + Add to another device Ajouter à un autre appareil No comment provided by engineer. + + Add to list + Ajouter à la liste + No comment provided by engineer. + Add welcome message Ajouter un message d'accueil No comment provided by engineer. + + Add your team members to the conversations. + Ajoutez les membres de votre équipe aux conversations. + No comment provided by engineer. + + + Added media & file servers + Ajout de serveurs de médias et de fichiers + No comment provided by engineer. + + + Added message servers + Ajout de serveurs de messages + No comment provided by engineer. + Additional accent Accent additionnel @@ -660,6 +701,16 @@ Le changement d'adresse sera annulé. L'ancienne adresse de réception sera utilisée. No comment provided by engineer. + + Address or 1-time link? + Adresse ou lien unique ? + No comment provided by engineer. + + + Address settings + Paramètres de l'adresse + No comment provided by engineer. + Admins can block a member for all. Les admins peuvent bloquer un membre pour tous. @@ -680,6 +731,11 @@ Paramètres avancés No comment provided by engineer. + + All + Tout + No comment provided by engineer. + All app data is deleted. Toutes les données de l'application sont supprimées. @@ -690,13 +746,18 @@ Toutes les discussions et tous les messages seront supprimés - il est impossible de revenir en arrière ! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Tous les chats seront supprimés de la liste %@, et la liste sera supprimée. + alert message + All data is erased when it is entered. Toutes les données sont effacées lorsqu'il est saisi. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Toutes les données restent confinées dans votre appareil. No comment provided by engineer. @@ -705,6 +766,11 @@ Tous les membres du groupe resteront connectés. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Tous les messages et fichiers sont envoyés **chiffrés de bout en bout**, avec une sécurité post-quantique dans les messages directs. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Tous les messages seront supprimés - il n'est pas possible de revenir en arrière ! @@ -723,6 +789,15 @@ All profiles Tous les profiles + profile dropdown + + + All reports will be archived for you. + Tous les rapports seront archivés pour vous. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -800,6 +875,11 @@ Autoriser la suppression irréversible de messages envoyés. (24 heures) No comment provided by engineer. + + Allow to report messsages to moderators. + Permettre de signaler des messages aux modérateurs. + No comment provided by engineer. + Allow to send SimpleX links. Autorise l'envoi de liens SimpleX. @@ -880,11 +960,21 @@ Un profil de chat vierge portant le nom fourni est créé et l'application s'ouvre normalement. No comment provided by engineer. + + Another reason + Autre raison + report reason + 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 : %@ @@ -900,6 +990,10 @@ L'application chiffre les nouveaux fichiers locaux (sauf les vidéos). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon Icône de l'app @@ -915,6 +1009,11 @@ Le code d'accès de l'application est remplacé par un code d'autodestruction. No comment provided by engineer. + + App session + Session de l'app + No comment provided by engineer. + App version Version de l'app @@ -940,6 +1039,21 @@ Appliquer à No comment provided by engineer. + + Archive + Archiver + No comment provided by engineer. + + + Archive %lld reports? + Archiver les rapports %lld ? + No comment provided by engineer. + + + Archive all reports? + Archiver tous les rapports ? + No comment provided by engineer. + Archive and upload Archiver et téléverser @@ -950,6 +1064,21 @@ Archiver les contacts pour discuter plus tard. No comment provided by engineer. + + Archive report + Archiver le rapport + No comment provided by engineer. + + + Archive report? + Archiver le rapport ? + No comment provided by engineer. + + + Archive reports + Archiver les rapports + swipe action + Archived contacts Contacts archivés @@ -1020,6 +1149,11 @@ Images auto-acceptées No comment provided by engineer. + + Auto-accept settings + Paramètres de réception automatique + alert title + Back Retour @@ -1045,11 +1179,26 @@ Mauvais hash de message No comment provided by engineer. + + Better calls + Appels améliorés + No comment provided by engineer. + Better groups Des groupes plus performants No comment provided by engineer. + + Better groups performance + Meilleure performance des groupes + No comment provided by engineer. + + + Better message dates. + Meilleures dates de messages. + No comment provided by engineer. + Better messages Meilleurs messages @@ -1060,6 +1209,26 @@ Meilleure gestion de réseau No comment provided by engineer. + + Better notifications + Notifications améliorées + No comment provided by engineer. + + + Better privacy and security + Meilleure protection de la privacité et de la sécurité + No comment provided by engineer. + + + Better security ✅ + Sécurité accrue ✅ + No comment provided by engineer. + + + Better user experience + Une meilleure expérience pour l'utilisateur + No comment provided by engineer. + Black Noir @@ -1140,11 +1309,35 @@ 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. + + Business address + Adresse professionnelle + No comment provided by engineer. + + + Business chats + Discussions professionnelles + No comment provided by engineer. + + + Businesses + Entreprises + 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: +- 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é ! @@ -1193,7 +1386,8 @@ Cancel Annuler - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1407,7 @@ Cannot receive file Impossible de recevoir le fichier - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1424,16 @@ Changer No comment provided by engineer. + + Change automatic message deletion? + Modifier la suppression automatique des messages ? + alert title + + + Change chat profiles + Changer de profil de discussion + authentication reason + Change database passphrase? Changer la phrase secrète de la base de données ? @@ -1274,11 +1478,21 @@ Change self-destruct passcode Modifier le code d'autodestruction authentication reason - set passcode view +set passcode view - - Chat archive - Archives du chat + + Chat + Discussions + No comment provided by engineer. + + + Chat already exists + La discussion existe déjà + No comment provided by engineer. + + + Chat already exists! + La discussion existe déjà ! No comment provided by engineer. @@ -1341,20 +1555,50 @@ Préférences de chat No comment provided by engineer. + + Chat preferences were changed. + Les préférences de discussion ont été modifiées. + alert message + + + Chat profile + Profil d'utilisateur + No comment provided by engineer. + Chat theme Thème de chat No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + La discussion sera supprimé pour tous les membres - cela ne peut pas être annulé ! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + Le discussion sera supprimé pour vous - il n'est pas possible de revenir en arrière ! + No comment provided by engineer. + Chats Discussions No comment provided by engineer. + + Check messages every 20 min. + Consulter les messages toutes les 20 minutes. + No comment provided by engineer. + + + Check messages when allowed. + Consulter les messages quand c'est possible. + No comment provided by engineer. + Check server address and try again. Vérifiez l'adresse du serveur et réessayez. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1650,16 @@ Effacer la conversation ? No comment provided by engineer. + + Clear group? + Vider le groupe ? + No comment provided by engineer. + + + Clear or delete group? + Vider ou supprimer le groupe ? + No comment provided by engineer. + Clear private notes? Effacer les notes privées ? @@ -1426,6 +1680,11 @@ Mode de couleur No comment provided by engineer. + + Community guidelines violation + Infraction aux règles communautaires + report reason + Compare file Comparer le fichier @@ -1441,14 +1700,49 @@ Complétées No comment provided by engineer. + + Conditions accepted on: %@. + Conditions acceptées le : %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Les conditions sont acceptées pour le(s) opérateur(s) : **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Les conditions sont déjà acceptées pour ces opérateurs : **%@**. + No comment provided by engineer. + + + Conditions of use + Conditions d'utilisation + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Les conditions seront acceptées pour le(s) opérateur(s) : **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Les conditions seront acceptées le : %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Les conditions seront automatiquement acceptées pour les opérateurs activés le : %@. + No comment provided by engineer. + Configure ICE servers Configurer les serveurs ICE No comment provided by engineer. - - Configured %@ servers - %@ serveurs configurés + + Configure server operators + Configurer les opérateurs de serveur No comment provided by engineer. @@ -1501,6 +1795,11 @@ Confirmer la transmission No comment provided by engineer. + + Confirmed + Confirmé + token status text + Connect Se connecter @@ -1620,6 +1919,11 @@ Il s'agit de votre propre lien unique ! État de la connexion et des serveurs. No comment provided by engineer. + + Connection blocked + Connexion bloquée + No comment provided by engineer. + Connection error Erreur de connexion @@ -1630,6 +1934,18 @@ Il s'agit de votre propre lien unique ! Erreur de connexion (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + La connexion est bloquée par l'opérateur du serveur : +%@ + No comment provided by engineer. + + + Connection not ready. + La connexion n'est pas prête. + No comment provided by engineer. + Connection notifications Notifications de connexion @@ -1640,6 +1956,16 @@ Il s'agit de votre propre lien unique ! Demande de connexion envoyée ! No comment provided by engineer. + + Connection requires encryption renegotiation. + La connexion nécessite une renégociation du cryptage. + No comment provided by engineer. + + + Connection security + Sécurité des connexions + No comment provided by engineer. + Connection terminated Connexion terminée @@ -1715,6 +2041,11 @@ Il s'agit de votre propre lien unique ! Vos contacts peuvent marquer les messages pour les supprimer ; vous pourrez les consulter. No comment provided by engineer. + + Content violates conditions of use + Le contenu enfreint les conditions d'utilisation + blocking reason + Continue Continuer @@ -1740,6 +2071,11 @@ Il s'agit de votre propre lien unique ! Version du cœur : v%@ No comment provided by engineer. + + Corner + Coin + No comment provided by engineer. + Correct name to %@? Corriger le nom pour %@ ? @@ -1750,6 +2086,11 @@ Il s'agit de votre propre lien unique ! Créer No comment provided by engineer. + + Create 1-time link + Créer un lien unique + No comment provided by engineer. + Create SimpleX address Créer une adresse SimpleX @@ -1760,11 +2101,6 @@ Il s'agit de votre propre lien unique ! Création de groupes via un profil aléatoire. No comment provided by engineer. - - Create an address to let people connect with you. - Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter. - No comment provided by engineer. - Create file Créer un fichier @@ -1785,6 +2121,11 @@ Il s'agit de votre propre lien unique ! Créer un lien No comment provided by engineer. + + Create list + Créer une liste + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Créer un nouveau profil sur [l'application de bureau](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2166,6 @@ Il s'agit de votre propre lien unique ! Créé à : %@ copied message info - - Created on %@ - Créé le %@ - No comment provided by engineer. - Creating archive link Création d'un lien d'archive @@ -1845,6 +2181,11 @@ Il s'agit de votre propre lien unique ! Code d'accès actuel No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Le texte sur les conditions actuelles n'a pas pu être chargé. Vous pouvez consulter les conditions en cliquant sur ce lien : + No comment provided by engineer. + Current passphrase… Phrase secrète actuelle… @@ -1865,6 +2206,11 @@ Il s'agit de votre propre lien unique ! Délai personnalisé No comment provided by engineer. + + Customizable message shape. + Forme des messages personnalisable. + No comment provided by engineer. + Customize theme Personnaliser le thème @@ -1996,8 +2342,8 @@ Il s'agit de votre propre lien unique ! Delete Supprimer - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2380,14 @@ Il s'agit de votre propre lien unique ! Supprimer et en informer le contact No comment provided by engineer. - - Delete archive - Supprimer l'archive + + Delete chat + Supprimer la discussion No comment provided by engineer. - - Delete chat archive? - Supprimer l'archive du chat ? + + Delete chat messages from your device. + Supprimer les messages de chat de votre appareil. No comment provided by engineer. @@ -2054,6 +2400,11 @@ Il s'agit de votre propre lien unique ! Supprimer le profil du chat ? No comment provided by engineer. + + Delete chat? + Supprimer la discussion ? + No comment provided by engineer. + Delete connection Supprimer la connexion @@ -2129,6 +2480,11 @@ Il s'agit de votre propre lien unique ! Supprimer le lien ? No comment provided by engineer. + + Delete list? + Supprimer la liste ? + alert title + Delete member message? Supprimer le message de ce membre ? @@ -2142,7 +2498,7 @@ Il s'agit de votre propre lien unique ! Delete messages Supprimer les messages - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2515,11 @@ Il s'agit de votre propre lien unique ! Supprimer l'ancienne base de données ? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Supprimer ou modérer jusqu'à 200 messages. + No comment provided by engineer. + Delete pending connection? Supprimer la connexion en attente ? @@ -2174,6 +2535,11 @@ Il s'agit de votre propre lien unique ! Supprimer la file d'attente server test step + + Delete report + Supprimer le rapport + No comment provided by engineer. + Delete up to 20 messages at once. Supprimez jusqu'à 20 messages à la fois. @@ -2209,6 +2575,11 @@ Il s'agit de votre propre lien unique ! Erreurs de suppression No comment provided by engineer. + + Delivered even when Apple drops them. + Distribués même quand Apple les oublie. + No comment provided by engineer. + Delivery Distribution @@ -2309,8 +2680,13 @@ Il s'agit de votre propre lien unique ! Messages directs chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + Les messages directs entre membres sont interdits dans cette discussion. + No comment provided by engineer. + + + Direct messages between members are prohibited. Les messages directs entre membres sont interdits dans ce groupe. No comment provided by engineer. @@ -2324,6 +2700,16 @@ Il s'agit de votre propre lien unique ! Désactiver SimpleX Lock authentication reason + + Disable automatic message deletion? + Désactiver la suppression automatique des messages ? + alert title + + + Disable delete messages + Désactiver la suppression des messages + alert button + Disable for all Désactiver pour tous @@ -2349,8 +2735,8 @@ Il s'agit de votre propre lien unique ! Les messages éphémères sont interdits dans cette discussion. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Les messages éphémères sont interdits dans ce groupe. No comment provided by engineer. @@ -2409,6 +2795,16 @@ Il s'agit de votre propre lien unique ! Ne pas envoyer d'historique aux nouveaux membres. No comment provided by engineer. + + Do not use credentials with proxy. + Ne pas utiliser d'identifiants avec le proxy. + No comment provided by engineer. + + + Documents: + Documents: + No comment provided by engineer. + Don't create address Ne pas créer d'adresse @@ -2419,11 +2815,21 @@ Il s'agit de votre propre lien unique ! Ne pas activer No comment provided by engineer. + + Don't miss important messages. + Ne manquez pas les messages importants. + No comment provided by engineer. + Don't show again Ne plus afficher No comment provided by engineer. + + Done + Terminé + No comment provided by engineer. + Downgrade and open chat Rétrograder et ouvrir le chat @@ -2432,7 +2838,8 @@ Il s'agit de votre propre lien unique ! Download Télécharger - chat item action + alert button +chat item action Download errors @@ -2449,6 +2856,11 @@ Il s'agit de votre propre lien unique ! Télécharger le fichier server test step + + Download files + Télécharger les fichiers + alert action + Downloaded Téléchargé @@ -2479,6 +2891,11 @@ Il s'agit de votre propre lien unique ! Durée No comment provided by engineer. + + E2E encrypted notifications. + Notifications chiffrées E2E. + No comment provided by engineer. + Edit Modifier @@ -2499,6 +2916,11 @@ Il s'agit de votre propre lien unique ! Activer (conserver les remplacements) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + Activez Flux dans les paramètres du réseau et des serveurs pour une meilleure confidentialité des métadonnées. + No comment provided by engineer. + Enable SimpleX Lock Activer SimpleX Lock @@ -2512,7 +2934,7 @@ Il s'agit de votre propre lien unique ! Enable automatic message deletion? Activer la suppression automatique des messages ? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3061,11 @@ Il s'agit de votre propre lien unique ! La renégociation du chiffrement a échoué. No comment provided by engineer. + + Encryption renegotiation in progress. + Renégociation du chiffrement en cours. + No comment provided by engineer. + Enter Passcode Entrer le code d'accès @@ -2704,26 +3131,36 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'annulation du changement d'adresse No comment provided by engineer. + + Error accepting conditions + Erreur lors de la validation des conditions + alert title + Error accepting contact request Erreur de validation de la demande de contact No comment provided by engineer. - - Error accessing database file - Erreur d'accès au fichier de la base de données - No comment provided by engineer. - Error adding member(s) Erreur lors de l'ajout de membre·s No comment provided by engineer. + + Error adding server + Erreur lors de l'ajout du serveur + alert title + Error changing address Erreur de changement d'adresse No comment provided by engineer. + + Error changing connection profile + Erreur lors du changement de profil de connexion + No comment provided by engineer. + Error changing role Erreur lors du changement de rôle @@ -2734,6 +3171,16 @@ Il s'agit de votre propre lien unique ! Erreur de changement de paramètre No comment provided by engineer. + + Error changing to incognito! + Erreur lors du passage en mode incognito ! + No comment provided by engineer. + + + Error checking token status + Erreur lors de la vérification de l'état du jeton (token) + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard. @@ -2754,6 +3201,11 @@ Il s'agit de votre propre lien unique ! Erreur lors de la création du lien du groupe No comment provided by engineer. + + Error creating list + Erreur lors de la création de la liste + alert title + Error creating member contact Erreur lors de la création du contact du membre @@ -2769,6 +3221,11 @@ Il s'agit de votre propre lien unique ! Erreur lors de la création du profil ! No comment provided by engineer. + + Error creating report + Erreur lors de la création du rapport + No comment provided by engineer. + Error decrypting file Erreur lors du déchiffrement du fichier @@ -2849,9 +3306,14 @@ Il s'agit de votre propre lien unique ! Erreur lors de la liaison avec le groupe No comment provided by engineer. - - Error loading %@ servers - Erreur lors du chargement des serveurs %@ + + Error loading servers + Erreur de chargement des serveurs + alert title + + + Error migrating settings + Erreur lors de la migration des paramètres No comment provided by engineer. @@ -2862,7 +3324,7 @@ Il s'agit de votre propre lien unique ! Error receiving file Erreur lors de la réception du fichier - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3336,36 @@ Il s'agit de votre propre lien unique ! Erreur de reconnexion des serveurs No comment provided by engineer. + + Error registering for notifications + Erreur lors de l'inscription aux notifications + alert title + Error removing member Erreur lors de la suppression d'un membre No comment provided by engineer. + + Error reordering lists + Erreur lors de la réorganisation des listes + alert title + Error resetting statistics Erreur de réinitialisation des statistiques No comment provided by engineer. - - Error saving %@ servers - Erreur lors de la sauvegarde des serveurs %@ - No comment provided by engineer. - Error saving ICE servers Erreur lors de la sauvegarde des serveurs ICE No comment provided by engineer. + + Error saving chat list + Erreur lors de l'enregistrement de la liste des chats + alert title + Error saving group profile Erreur lors de la sauvegarde du profil de groupe @@ -2909,6 +3381,11 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'enregistrement de la phrase de passe dans la keychain No comment provided by engineer. + + Error saving servers + Erreur d'enregistrement des serveurs + alert title + Error saving settings Erreur lors de l'enregistrement des paramètres @@ -2954,16 +3431,26 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'arrêt du chat No comment provided by engineer. + + Error switching profile + Erreur lors du changement de profil + No comment provided by engineer. + Error switching profile! Erreur lors du changement de profil ! - No comment provided by engineer. + alertTitle Error synchronizing connection Erreur de synchronisation de connexion No comment provided by engineer. + + Error testing server connection + Erreur lors du test de connexion au serveur + No comment provided by engineer. + Error updating group link Erreur lors de la mise à jour du lien de groupe @@ -2974,6 +3461,11 @@ Il s'agit de votre propre lien unique ! Erreur lors de la mise à jour du message No comment provided by engineer. + + Error updating server + Erreur de mise à jour du serveur + alert title + Error updating settings Erreur lors de la mise à jour des paramètres @@ -3002,8 +3494,9 @@ Il s'agit de votre propre lien unique ! Error: %@ Erreur : %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3513,11 @@ Il s'agit de votre propre lien unique ! Erreurs No comment provided by engineer. + + Errors in servers configuration. + Erreurs dans la configuration des serveurs. + servers error + Even when disabled in the conversation. Même s'il est désactivé dans la conversation. @@ -3035,6 +3533,11 @@ Il s'agit de votre propre lien unique ! Étendre chat item action + + Expired + Expiré + token status text + Export database Exporter la base de données @@ -3075,20 +3578,49 @@ Il s'agit de votre propre lien unique ! Rapide et ne nécessitant pas d'attendre que l'expéditeur soit en ligne ! No comment provided by engineer. + + Faster deletion of groups. + Suppression plus rapide des groupes. + No comment provided by engineer. + Faster joining and more reliable messages. Connexion plus rapide et messages plus fiables. No comment provided by engineer. + + Faster sending messages. + Envoi plus rapide des messages. + No comment provided by engineer. + Favorite Favoris swipe action + + Favorites + Favoris + No comment provided by engineer. + File error Erreur de fichier - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Erreurs de fichier : +%@ + alert message + + + File is blocked by server operator: +%@. + Le fichier est bloqué par l'opérateur du serveur : +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,8 +3677,8 @@ Il s'agit de votre propre lien unique ! Fichiers et médias chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Les fichiers et les médias sont interdits dans ce groupe. No comment provided by engineer. @@ -3215,21 +3747,69 @@ Il s'agit de votre propre lien unique ! Correction non prise en charge par un membre du groupe No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + Pour le profil de discussion %@ : + servers error + For console Pour la console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Par exemple, si votre contact reçoit des messages via un serveur SimpleX Chat, votre application les transmettra via un serveur Flux. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + Pour le routage privé + No comment provided by engineer. + + + For social media + Pour les réseaux sociaux + No comment provided by engineer. + Forward Transférer chat item action + + Forward %d message(s)? + Transférer %d message(s) ? + alert title + Forward and save messages Transférer et sauvegarder des messages No comment provided by engineer. + + Forward messages + Transférer les messages + alert action + + + Forward messages without files? + Transférer les messages sans les fichiers ? + alert message + + + Forward up to 20 messages at once. + Transférez jusqu'à 20 messages à la fois. + No comment provided by engineer. + Forwarded Transféré @@ -3240,6 +3820,11 @@ Il s'agit de votre propre lien unique ! Transféré depuis No comment provided by engineer. + + Forwarding %lld messages + Transfert des %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard. @@ -3289,11 +3874,6 @@ Erreur : %2$@ Nom complet (optionnel) No comment provided by engineer. - - Full name: - Nom complet : - No comment provided by engineer. - Fully decentralized – visible only to members. Entièrement décentralisé – visible que par ses membres. @@ -3314,6 +3894,10 @@ Erreur : %2$@ GIFs et stickers No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! Bonjour ! @@ -3379,41 +3963,6 @@ Erreur : %2$@ Liens de groupe No comment provided by engineer. - - Group members can add message reactions. - Les membres du groupe peuvent ajouter des réactions aux messages. - No comment provided by engineer. - - - Group 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) - No comment provided by engineer. - - - Group members can send SimpleX links. - Les membres du groupe peuvent envoyer des liens SimpleX. - No comment provided by engineer. - - - Group members can send direct messages. - Les membres du groupe peuvent envoyer des messages directs. - No comment provided by engineer. - - - Group members can send disappearing messages. - Les membres du groupes peuvent envoyer des messages éphémères. - No comment provided by engineer. - - - Group members can send files and media. - Les membres du groupe peuvent envoyer des fichiers et des médias. - No comment provided by engineer. - - - Group members can send voice messages. - Les membres du groupe peuvent envoyer des messages vocaux. - No comment provided by engineer. - Group message: Message du groupe : @@ -3454,11 +4003,19 @@ Erreur : %2$@ Le groupe va être supprimé pour vous - impossible de revenir en arrière ! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Aide No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Caché @@ -3509,10 +4066,19 @@ Erreur : %2$@ Comment SimpleX fonctionne No comment provided by engineer. + + How it affects privacy + L'impact sur la vie privée + No comment provided by engineer. + + + How it helps privacy + Comment il contribue à la protection de la vie privée + No comment provided by engineer. + How it works - Comment ça fonctionne - No comment provided by engineer. + alert button How to @@ -3539,6 +4105,11 @@ Erreur : %2$@ Serveurs ICE (un par ligne) No comment provided by engineer. + + IP address + Adresse IP + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Si vous ne pouvez pas vous rencontrer en personne, montrez le code QR lors d'un appel vidéo ou partagez le lien. @@ -3579,8 +4150,8 @@ Erreur : %2$@ Immédiatement No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Protégé du spam et des abus No comment provided by engineer. @@ -3614,6 +4185,13 @@ Erreur : %2$@ Importation de l'archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Amélioration de la distribution, réduction de l'utilisation du trafic. +D'autres améliorations sont à venir ! + No comment provided by engineer. + Improved message delivery Amélioration de la transmission des messages @@ -3644,6 +4222,14 @@ Erreur : %2$@ Sons d'appel No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Incognito @@ -3714,6 +4300,11 @@ Erreur : %2$@ Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Instantané + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4312,6 @@ Erreur : %2$@ No comment provided by engineer. - - Instantly - Instantané - No comment provided by engineer. - Interface Interface @@ -3736,6 +4322,26 @@ Erreur : %2$@ Couleurs d'interface No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code Code QR invalide @@ -3774,7 +4380,7 @@ Erreur : %2$@ Invalid server address! Adresse de serveur invalide ! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4402,11 @@ Erreur : %2$@ Inviter des membres No comment provided by engineer. + + Invite to chat + Inviter à discuter + No comment provided by engineer. + Invite to group Inviter au groupe @@ -3811,8 +4422,8 @@ Erreur : %2$@ La suppression irréversible de message est interdite dans ce chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. La suppression irréversible de messages est interdite dans ce groupe. No comment provided by engineer. @@ -3902,7 +4513,7 @@ Voici votre lien pour le groupe %@ ! Keep Conserver - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4528,7 @@ Voici votre lien pour le groupe %@ ! Keep unused invitation? Conserver l'invitation inutilisée ? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4565,16 @@ Voici votre lien pour le groupe %@ ! Quitter swipe action + + Leave chat + Quitter la discussion + No comment provided by engineer. + + + Leave chat? + Quitter la discussion ? + No comment provided by engineer. + Leave group Quitter le groupe @@ -3994,6 +4615,18 @@ Voici votre lien pour le groupe %@ ! Bureaux liés No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Message dynamique ! @@ -4004,11 +4637,6 @@ Voici votre lien pour le groupe %@ ! Messages dynamiques No comment provided by engineer. - - Local - Local - No comment provided by engineer. - Local name Nom local @@ -4029,11 +4657,6 @@ Voici votre lien pour le groupe %@ ! Mode de verrouillage No comment provided by engineer. - - Make a private connection - Établir une connexion privée - No comment provided by engineer. - Make one message disappear Rendre un message éphémère @@ -4044,21 +4667,11 @@ Voici votre lien pour le groupe %@ ! Rendre un profil privé ! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Assurez-vous que les adresses des serveurs %@ sont au bon format et ne sont pas dupliquées, un par ligne (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Beaucoup se demandent : *si SimpleX n'a pas d'identifiant d'utilisateur, comment peut-il délivrer des messages ?* - No comment provided by engineer. - Mark deleted for everyone Marquer comme supprimé pour tout le monde @@ -4104,6 +4717,15 @@ Voici votre lien pour le groupe %@ ! Membre inactif item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Le rôle du membre sera modifié pour « %@ ». Tous les membres du chat seront notifiés. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Le rôle du membre sera changé pour "%@". Tous les membres du groupe en seront informés. @@ -4114,11 +4736,59 @@ Voici votre lien pour le groupe %@ ! Le rôle du membre sera changé pour "%@". Ce membre recevra une nouvelle invitation. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Le membre sera retiré de la discussion - cela ne peut pas être annulé ! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Ce membre sera retiré du groupe - impossible de revenir en arrière ! No comment provided by engineer. + + Members can add message reactions. + Les membres du groupe peuvent ajouter des réactions aux messages. + 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) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Les membres du groupe peuvent envoyer des liens SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Les membres du groupe peuvent envoyer des messages directs. + No comment provided by engineer. + + + Members can send disappearing messages. + Les membres du groupes peuvent envoyer des messages éphémères. + No comment provided by engineer. + + + Members can send files and media. + Les membres du groupe peuvent envoyer des fichiers et des médias. + No comment provided by engineer. + + + Members can send voice messages. + Les membres du groupe peuvent envoyer des messages vocaux. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus Menus @@ -4169,8 +4839,8 @@ Voici votre lien pour le groupe %@ ! Les réactions aux messages sont interdites dans ce chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Les réactions aux messages sont interdites dans ce groupe. No comment provided by engineer. @@ -4184,6 +4854,11 @@ Voici votre lien pour le groupe %@ ! Serveurs de messages No comment provided by engineer. + + Message shape + Forme du message + No comment provided by engineer. + Message source remains private. La source du message reste privée. @@ -4224,6 +4899,10 @@ Voici votre lien pour le groupe %@ ! Les messages de %@ seront affichés ! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received Messages reçus @@ -4234,6 +4913,11 @@ Voici votre lien pour le groupe %@ ! Messages envoyés No comment provided by engineer. + + Messages were deleted after you selected them. + Les messages ont été supprimés après avoir été sélectionnés. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. @@ -4299,9 +4983,9 @@ Voici votre lien pour le groupe %@ ! La migration est terminée No comment provided by engineer. - - Migrations: %@ - Migrations : %@ + + Migrations: + Migrations : No comment provided by engineer. @@ -4319,6 +5003,10 @@ Voici votre lien pour le groupe %@ ! Modéré à : %@ copied message info + + More + swipe action + More improvements are coming soon! Plus d'améliorations à venir ! @@ -4329,6 +5017,11 @@ Voici votre lien pour le groupe %@ ! Connexion réseau plus fiable. No comment provided by engineer. + + More reliable notifications + Notifications plus fiables + No comment provided by engineer. + Most likely this connection is deleted. Connexion probablement supprimée. @@ -4342,7 +5035,11 @@ Voici votre lien pour le groupe %@ ! Mute Muet - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4364,6 +5061,11 @@ Voici votre lien pour le groupe %@ ! Connexion au réseau No comment provided by engineer. + + Network decentralization + Décentralisation du réseau + No comment provided by engineer. + 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. @@ -4374,6 +5076,11 @@ Voici votre lien pour le groupe %@ ! Gestion du réseau No comment provided by engineer. + + Network operator + Opérateur de réseau + No comment provided by engineer. + Network settings Paramètres réseau @@ -4384,11 +5091,25 @@ Voici votre lien pour le groupe %@ ! État du réseau No comment provided by engineer. + + New + token status text + New Passcode Nouveau code d'accès No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + De nouveaux identifiants SOCKS seront utilisés chaque fois que vous démarrerez l'application. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + De nouveaux identifiants SOCKS seront utilisées pour chaque serveur. + No comment provided by engineer. + New chat Nouveau chat @@ -4409,11 +5130,6 @@ Voici votre lien pour le groupe %@ ! Nouveau contact : notification - - New database archive - Nouvelle archive de base de données - No comment provided by engineer. - New desktop app! Nouvelle application de bureau ! @@ -4424,6 +5140,11 @@ Voici votre lien pour le groupe %@ ! Nouveau nom d'affichage No comment provided by engineer. + + New events + Nouveaux événements + notification + New in %@ Nouveautés de la %@ @@ -4449,6 +5170,11 @@ Voici votre lien pour le groupe %@ ! Nouvelle phrase secrète… No comment provided by engineer. + + New server + Nouveau serveur + No comment provided by engineer. + No Non @@ -4459,6 +5185,18 @@ Voici votre lien pour le groupe %@ ! Pas de mot de passe pour l'app Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Aucun contact sélectionné @@ -4504,31 +5242,102 @@ Voici votre lien pour le groupe %@ ! Pas d'info, essayez de recharger No comment provided by engineer. + + No media & file servers. + Pas de serveurs de médias et de fichiers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + Pas de serveurs de messages. + servers error + No network connection Pas de connexion au réseau No comment provided by engineer. + + No permission to record speech + Enregistrement des conversations non autorisé + No comment provided by engineer. + + + No permission to record video + Enregistrement de la vidéo non autorisé + No comment provided by engineer. + No permission to record voice message Pas l'autorisation d'enregistrer un message vocal No comment provided by engineer. + + No push server + No push server + No comment provided by engineer. + No received or sent files Aucun fichier reçu ou envoyé No comment provided by engineer. + + No servers for private message routing. + Pas de serveurs pour le routage privé des messages. + servers error + + + No servers to receive files. + Pas de serveurs pour recevoir des fichiers. + servers error + + + No servers to receive messages. + Pas de serveurs pour recevoir des messages. + servers error + + + No servers to send files. + Pas de serveurs pour envoyer des fichiers. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + Aucun identifiant d'utilisateur. + No comment provided by engineer. + Not compatible! Non compatible ! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected Aucune sélection No comment provided by engineer. + + Nothing to forward! + Rien à transférer ! + alert title + Notifications Notifications @@ -4539,6 +5348,19 @@ Voici votre lien pour le groupe %@ ! Les notifications sont désactivées ! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + Notifications sécurisées + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5383,13 @@ Voici votre lien pour le groupe %@ ! Ok Ok - No comment provided by engineer. + alert button Old database Ancienne base de données No comment provided by engineer. - - Old database archive - Archives de l'ancienne base de données - No comment provided by engineer. - One-time invitation link Lien d'invitation unique @@ -4597,8 +5414,13 @@ Nécessite l'activation d'un VPN. Les hôtes .onion ne seront pas utilisés. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Seuls les propriétaires peuvent modifier les préférences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**. No comment provided by engineer. @@ -4622,6 +5444,14 @@ Nécessite l'activation d'un VPN. Seuls les propriétaires de groupes peuvent activer les messages vocaux. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Vous seul pouvez ajouter des réactions aux messages. @@ -4675,13 +5505,18 @@ Nécessite l'activation d'un VPN. Open Ouvrir - No comment provided by engineer. + alert action Open Settings Ouvrir les Paramètres No comment provided by engineer. + + Open changes + Ouvrir les modifications + No comment provided by engineer. + Open chat Ouvrir le chat @@ -4692,36 +5527,45 @@ Nécessite l'activation d'un VPN. Ouvrir la console du chat authentication reason + + Open conditions + Ouvrir les conditions + No comment provided by engineer. + Open group Ouvrir le groupe No comment provided by engineer. + + Open link? + alert title + Open migration to another device Ouvrir le transfert vers un autre appareil authentication reason - - Open server settings - Ouvrir les paramètres du serveur - No comment provided by engineer. - - - Open user profiles - Ouvrir les profils d'utilisateurs - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protocole et code open-source – n'importe qui peut heberger un serveur. - No comment provided by engineer. - Opening app… Ouverture de l'app… No comment provided by engineer. + + Operator + Opérateur + No comment provided by engineer. + + + Operator server + Serveur de l'opérateur + alert title + + + Or import archive file + Ou importer un fichier d'archive + No comment provided by engineer. + Or paste archive link Ou coller le lien de l'archive @@ -4739,7 +5583,16 @@ Nécessite l'activation d'un VPN. Or show this code - Ou présenter ce code + Ou montrez ce code + No comment provided by engineer. + + + Or to share privately + Ou à partager en privé + No comment provided by engineer. + + + Organize chats into lists No comment provided by engineer. @@ -4747,10 +5600,12 @@ Nécessite l'activation d'un VPN. Autres No comment provided by engineer. - - Other %@ servers - Autres serveurs %@ - No comment provided by engineer. + + Other file errors: +%@ + Autres erreurs de fichiers : +%@ + alert message PING count @@ -4787,6 +5642,11 @@ Nécessite l'activation d'un VPN. Code d'accès défini ! No comment provided by engineer. + + Password + Mot de passe + No comment provided by engineer. + Password to show Mot de passe à entrer @@ -4822,13 +5682,8 @@ Nécessite l'activation d'un VPN. En attente No comment provided by engineer. - - People can connect to you only via the links you share. - On ne peut se connecter à vous qu’avec les liens que vous partagez. - No comment provided by engineer. - - - Periodically + + Periodic Périodique No comment provided by engineer. @@ -4931,11 +5786,28 @@ Erreur : %@ Veuillez conserver votre phrase secrète en lieu sûr, vous NE pourrez PAS la changer si vous la perdez. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Interface en polonais No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte @@ -4946,16 +5818,16 @@ Erreur : %@ Conserver le brouillon du dernier message, avec les pièces jointes. No comment provided by engineer. - - Preset server - Serveur prédéfini - No comment provided by engineer. - Preset server address Adresse du serveur prédéfinie No comment provided by engineer. + + Preset servers + Serveurs prédéfinis + No comment provided by engineer. + Preview Aperçu @@ -4971,16 +5843,33 @@ Erreur : %@ Vie privée et sécurité No comment provided by engineer. + + Privacy for your customers. + Respect de la vie privée de vos clients. + No comment provided by engineer. + + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined La vie privée redéfinie No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Noms de fichiers privés No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing Routage privé des messages @@ -5021,16 +5910,6 @@ Erreur : %@ Images de profil No comment provided by engineer. - - Profile name - Nom du profil - No comment provided by engineer. - - - Profile name: - Nom du profil : - No comment provided by engineer. - Profile password Mot de passe de profil @@ -5044,7 +5923,7 @@ Erreur : %@ Profile update will be sent to your contacts. La mise à jour du profil sera envoyée à vos contacts. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5945,10 @@ Erreur : %@ Interdire les réactions aux messages. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. Interdire l'envoi de liens SimpleX. @@ -5133,6 +6016,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Serveurs routés via des proxy No comment provided by engineer. + + Proxy requires password + Le proxy est protégé par un mot de passe + No comment provided by engineer. + Push notifications Notifications push @@ -5173,26 +6061,21 @@ 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/app-settings.html#your-simplex-contact-address). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Plus d'informations sur notre GitHub. - 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). @@ -5323,11 +6206,23 @@ Activez-le dans les paramètres *Réseau et serveurs*. Réduction de la consommation de batterie No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Rejeter reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6249,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Supprimer No comment provided by engineer. + + Remove archive? + Supprimer l'archive ? + No comment provided by engineer. + Remove image Enlever l'image @@ -5419,6 +6319,46 @@ Activez-le dans les paramètres *Réseau et serveurs*. Répondre chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Requis @@ -5504,6 +6444,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Révéler chat item action + + Review conditions + Vérifier les conditions + No comment provided by engineer. + Revoke Révoquer @@ -5534,6 +6479,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Serveur SMP No comment provided by engineer. + + SOCKS proxy + proxy SOCKS + No comment provided by engineer. + Safely receive files Réception de fichiers en toute sécurité @@ -5547,17 +6497,18 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save Enregistrer - chat item action + alert button +chat item action Save (and notify contacts) Enregistrer (et en informer les contacts) - No comment provided by engineer. + alert button Save and notify contact Enregistrer et en informer le contact - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6525,15 @@ Activez-le dans les paramètres *Réseau et serveurs*. Enregistrer et mettre à jour le profil du groupe No comment provided by engineer. - - Save archive - Enregistrer l'archive - No comment provided by engineer. - - - Save auto-accept settings - Enregistrer les paramètres de validation automatique - No comment provided by engineer. - Save group profile Enregistrer le profil du groupe No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Enregistrer la phrase secrète et ouvrir le chat @@ -5602,7 +6547,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save preferences? Enregistrer les préférences ? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6562,18 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save servers? Enregistrer les serveurs ? - No comment provided by engineer. - - - Save settings? - Enregistrer les paramètres ? - No comment provided by engineer. + alert title Save welcome message? Enregistrer le message d'accueil ? No comment provided by engineer. + + Save your profile? + Sauvegarder votre profil ? + alert title + Saved Enregistré @@ -5649,6 +6594,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Message enregistré message info title + + Saving %lld messages + Sauvegarde de %lld messages + No comment provided by engineer. + Scale Échelle @@ -5656,7 +6606,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Scan / Paste link - Scanner / Coller le lien + Scanner / Coller un lien No comment provided by engineer. @@ -5729,6 +6679,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Choisir chat item action + + Select chat profile + Sélectionner un profil de discussion + No comment provided by engineer. + Selected %lld %lld sélectionné(s) @@ -5819,9 +6774,8 @@ Activez-le dans les paramètres *Réseau et serveurs*. Envoi de notifications No comment provided by engineer. - - Send notifications: - Envoi de notifications : + + Send private reports No comment provided by engineer. @@ -5847,7 +6801,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Sender cancelled file transfer. L'expéditeur a annulé le transfert de fichiers. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6898,16 @@ Activez-le dans les paramètres *Réseau et serveurs*. Envoyé via le proxy No comment provided by engineer. + + Server + Serveur + No comment provided by engineer. + + + Server added to operator %@. + Serveur ajouté à l'opérateur %@. + alert message + Server address Adresse du serveur @@ -5959,6 +6923,21 @@ Activez-le dans les paramètres *Réseau et serveurs*. L'adresse du serveur est incompatible avec les paramètres réseau : %@. No comment provided by engineer. + + Server operator changed. + L'opérateur du serveur a changé. + alert title + + + Server operators + Opérateurs de serveur + No comment provided by engineer. + + + Server protocol changed. + Le protocole du serveur a été modifié. + alert title + Server requires authorization to create queues, check password Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe @@ -6014,6 +6993,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Définir 1 jour No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Définir le nom du contact… @@ -6034,6 +7017,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Il permet de remplacer l'authentification du système. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Définir le code d'accès @@ -6064,6 +7051,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Paramètres No comment provided by engineer. + + Settings were changed. + Les paramètres ont été modifiés. + alert message + Shape profile images Images de profil modelable @@ -6072,22 +7064,38 @@ Activez-le dans les paramètres *Réseau et serveurs*. Share Partager - chat item action + alert action +chat item action Share 1-time link Partager un lien unique No comment provided by engineer. + + Share 1-time link with a friend + Partager un lien unique avec un ami + No comment provided by engineer. + + + Share SimpleX address on social media. + Partagez votre adresse SimpleX sur les réseaux sociaux. + No comment provided by engineer. + Share address Partager l'adresse No comment provided by engineer. + + Share address publicly + Partager publiquement votre adresse + No comment provided by engineer. + Share address with contacts? Partager l'adresse avec vos contacts ? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,9 +7107,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. Partager le lien No comment provided by engineer. + + Share profile + Partager le profil + No comment provided by engineer. + Share this 1-time invite link - Partager ce lien d'invitation unique + Partagez ce lien d'invitation unique No comment provided by engineer. @@ -6114,6 +7127,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Partager avec vos contacts No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Afficher le code QR @@ -6169,6 +7186,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Adresse SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat et Flux ont conclu un accord pour inclure les serveurs exploités par Flux dans l'application. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. La sécurité de SimpleX Chat a été auditée par Trail of Bits. @@ -6199,6 +7221,20 @@ Activez-le dans les paramètres *Réseau et serveurs*. Adresse SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + Les adresses SimpleX et les liens à usage unique peuvent être partagés en toute sécurité via n'importe quelle messagerie. + No comment provided by engineer. + + + SimpleX address or 1-time link? + Adresse SimpleX ou lien unique ? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address Adresse de contact SimpleX @@ -6219,8 +7255,8 @@ Activez-le dans les paramètres *Réseau et serveurs*. Liens SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Les liens SimpleX sont interdits dans ce groupe. No comment provided by engineer. @@ -6234,6 +7270,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Invitation unique SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + Protocoles SimpleX audité par Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Mode incognito simplifié @@ -6264,6 +7305,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. Léger blur media + + Some app settings were not migrated. + Certains paramètres de l'application n'ont pas été migrés. + No comment provided by engineer. + Some file(s) were not exported: Certains fichiers n'ont pas été exportés : @@ -6279,11 +7325,23 @@ Activez-le dans les paramètres *Réseau et serveurs*. L'importation a entraîné des erreurs non fatales : No comment provided by engineer. + + Some servers failed the test: +%@ + Certains serveurs ont échoué le test : +%@ + alert message + Somebody Quelqu'un notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. Carré, circulaire, ou toute autre forme intermédiaire. @@ -6329,11 +7387,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Arrêter le chat No comment provided by engineer. - - Stop chat to enable database actions - Arrêter le chat pour permettre des actions sur la base de données - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Arrêtez le chat pour exporter, importer ou supprimer la base de données du chat. Vous ne pourrez pas recevoir et envoyer de messages pendant que le chat est arrêté. @@ -6362,18 +7415,22 @@ Activez-le dans les paramètres *Réseau et serveurs*. Stop sharing Cesser le partage - No comment provided by engineer. + alert action Stop sharing address? Cesser le partage d'adresse ? - No comment provided by engineer. + alert title Stopping chat Arrêt du chat No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong Fort @@ -6404,6 +7461,16 @@ Activez-le dans les paramètres *Réseau et serveurs*. Supporter SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Passer de l'audio à la vidéo pendant l'appel. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Changer de profil de chat pour les invitations à usage unique. + No comment provided by engineer. + System Système @@ -6424,6 +7491,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Délai de connexion TCP No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7510,21 @@ Activez-le dans les paramètres *Réseau et serveurs*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Queue + No comment provided by engineer. + Take picture Prendre une photo 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 button Appuyez sur le bouton @@ -6482,13 +7563,17 @@ Activez-le dans les paramètres *Réseau et serveurs*. Temporary file error Erreur de fichier temporaire - No comment provided by engineer. + file error alert title Test failed at step %@. Échec du test à l'étape %@. server test failure + + Test notifications + No comment provided by engineer. + Test server Tester le serveur @@ -6502,7 +7587,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Tests failed! Échec des tests ! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7604,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Merci aux utilisateurs - contribuez via Weblate ! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - La 1ère plateforme sans aucun identifiant d'utilisateur – privée par design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7616,11 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + L'application protège votre vie privée en utilisant des opérateurs différents pour chaque conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). L'application demandera de confirmer les téléchargements à partir de serveurs de fichiers inconnus (sauf .onion). @@ -6551,6 +7636,11 @@ 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, your contact may be offline. + La connexion a atteint la limite des messages non délivrés, votre contact est peut-être hors ligne. + No comment provided by engineer. + The connection you accepted will be cancelled! La connexion que vous avez acceptée sera annulée ! @@ -6571,6 +7661,11 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. Le hash du message précédent est différent. @@ -6596,19 +7691,19 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Les messages seront marqués comme modérés pour tous les membres. No comment provided by engineer. - - The next generation of private messaging - La nouvelle génération de messagerie privée - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. 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 profile is only shared with your contacts. - Le profil n'est partagé qu'avec vos contacts. + + The same conditions will apply to operator **%@**. + Les mêmes conditions s'appliquent à l'opérateur **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + Le deuxième opérateur prédéfini de l'application ! No comment provided by engineer. @@ -6626,16 +7721,31 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Les serveurs pour les nouvelles connexions de votre profil de chat actuel **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + Les serveurs pour les nouveaux fichiers de votre profil de chat actuel **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Le texte collé n'est pas un lien SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + L'archive de la base de données envoyée sera définitivement supprimée des serveurs. + No comment provided by engineer. + Themes Thèmes No comment provided by engineer. + + These conditions will also apply for: **%@**. + Ces conditions s'appliquent également aux : **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Ces paramètres s'appliquent à votre profil actuel **%@**. @@ -6656,6 +7766,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Cette action ne peut être annulée - les messages envoyés et reçus avant la date sélectionnée seront supprimés. Cela peut prendre plusieurs minutes. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Cette action ne peut être annulée - votre profil, vos contacts, vos messages et vos fichiers seront irréversiblement perdus. @@ -6701,11 +7815,19 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Voici votre propre lien unique ! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Ce lien a été utilisé avec un autre appareil mobile, veuillez créer un nouveau lien sur le bureau. No comment provided by engineer. + + This message was deleted or not received yet. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Ce paramètre s'applique aux messages de votre profil de chat actuel **%@**. @@ -6723,7 +7845,7 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. To connect, your contact can scan QR code or use the link in the app. - Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application. + Pour se connecter, votre contact peut scanner un code QR ou utiliser un lien dans l'app. No comment provided by engineer. @@ -6736,9 +7858,9 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Pour établir une nouvelle connexion No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts. + + To protect against your link being replaced, you can compare contact security codes. + Pour vous protéger contre le remplacement de votre lien, vous pouvez comparer les codes de sécurité des contacts. No comment provided by engineer. @@ -6758,6 +7880,26 @@ You will be prompted to complete authentication before this feature is enabled.< Vous serez invité à confirmer l'authentification avant que cette fonction ne soit activée. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts. + No comment provided by engineer. + + + To receive + Pour recevoir + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Si vous souhaitez enregistrer une conversation, veuillez autoriser l'utilisation du microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Si vous souhaitez enregistrer une vidéo, veuillez autoriser l'utilisation de la caméra. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Pour enregistrer un message vocal, veuillez accorder la permission d'utiliser le microphone. @@ -6768,11 +7910,21 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Pour révéler votre profil caché, entrez le mot de passe dans le champ de recherche de la page **Vos profils de chat**. No comment provided by engineer. + + To send + Pour envoyer + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Pour prendre en charge les notifications push instantanées, la base de données du chat doit être migrée. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + Pour utiliser les serveurs de **%@**, acceptez les conditions d'utilisation. + 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. @@ -6788,6 +7940,10 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Basculer en mode incognito lors de la connexion. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity Opacité de la barre d'outils @@ -6863,6 +8019,11 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Débloquer ce membre ? No comment provided by engineer. + + Undelivered messages + Messages non distribués + No comment provided by engineer. + Unexpected migration state État de la migration inattendu @@ -6911,7 +8072,7 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Unknown servers! Serveurs inconnus ! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +8109,17 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Unmute Démute - swipe action + notification label action Unread Non lu swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Les 100 derniers messages sont envoyés aux nouveaux membres. @@ -6980,6 +8145,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Mettre à jour les paramètres ? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. La mise à jour des ces paramètres reconnectera le client à tous les serveurs. @@ -7020,16 +8189,34 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Envoi de l'archive No comment provided by engineer. + + Use %@ + Utiliser %@ + No comment provided by engineer. + Use .onion hosts Utiliser les hôtes .onions No comment provided by engineer. + + Use SOCKS proxy + Utiliser un proxy SOCKS + No comment provided by engineer. + Use SimpleX Chat servers? Utiliser les serveurs SimpleX Chat ? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Utiliser le chat @@ -7040,6 +8227,16 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser le profil actuel No comment provided by engineer. + + Use for files + Utiliser pour les fichiers + No comment provided by engineer. + + + Use for messages + Utiliser pour les messages + No comment provided by engineer. + Use for new connections Utiliser pour les nouvelles connexions @@ -7080,6 +8277,15 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser ce serveur No comment provided by engineer. + + Use servers + Utiliser les serveurs + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Utiliser l'application pendant l'appel. @@ -7090,9 +8296,8 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser l'application d'une main. No comment provided by engineer. - - User profile - Profil d'utilisateur + + Use web port No comment provided by engineer. @@ -7100,6 +8305,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Sélection de l'utilisateur No comment provided by engineer. + + Username + Nom d'utilisateur + No comment provided by engineer. + Using SimpleX Chat servers. Vous utilisez les serveurs SimpleX. @@ -7170,11 +8380,21 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Vidéos et fichiers jusqu'à 1Go No comment provided by engineer. + + View conditions + Voir les conditions + No comment provided by engineer. + View security code Afficher le code de sécurité No comment provided by engineer. + + View updated conditions + Voir les conditions mises à jour + No comment provided by engineer. + Visible history Historique visible @@ -7190,8 +8410,8 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Les messages vocaux sont interdits dans ce chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Les messages vocaux sont interdits dans ce groupe. No comment provided by engineer. @@ -7285,9 +8505,9 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Lors des appels audio et vidéo. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Vous pouvez accepter ou refuser les demandes de contacts. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Lorsque plusieurs opérateurs sont activés, aucun d'entre eux ne dispose de métadonnées permettant de savoir qui communique avec qui. No comment provided by engineer. @@ -7333,7 +8553,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8580,6 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Serveur XFTP No comment provided by engineer. - - You - Vous - No comment provided by engineer. - You **must not** use the same database on two devices. Vous **ne devez pas** utiliser la même base de données sur deux appareils. @@ -7390,6 +8605,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Vous êtes déjà connecté·e à %@ via ce lien. No comment provided by engineer. + + You are already connected with %@. + Vous êtes déjà connecté avec %@. + No comment provided by engineer. + You are already connecting to %@. Vous êtes déjà en train de vous connecter à %@. @@ -7452,6 +8672,11 @@ Répéter la demande d'adhésion ? Vous pouvez choisir de le modifier dans les paramètres d'apparence. No comment provided by engineer. + + You can configure servers via settings. + Vous pouvez configurer les serveurs via les paramètres. + No comment provided by engineer. + You can create it later Vous pouvez la créer plus tard @@ -7492,6 +8717,11 @@ Répéter la demande d'adhésion ? Vous pouvez envoyer des messages à %@ à partir des contacts archivés. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + Vous pouvez définir un nom de connexion pour vous rappeler avec qui le lien a été partagé. + No comment provided by engineer. + You can set lock screen notification preview via settings. Vous pouvez configurer l'aperçu des notifications sur l'écran de verrouillage via les paramètres. @@ -7507,11 +8737,6 @@ Répéter la demande d'adhésion ? Vous pouvez partager cette adresse avec vos contacts pour leur permettre de se connecter avec **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Vous pouvez partager votre adresse sous la forme d'un lien ou d'un code QR - tout le monde peut l'utiliser pour vous contacter. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app @@ -7535,23 +8760,23 @@ Répéter la demande d'adhésion ? You can view invitation link again in connection details. Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion. - No comment provided by engineer. + alert message You can't send messages! Vous ne pouvez pas envoyer de messages ! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** les messages de vos contacts. - 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. + No comment provided by engineer. + You have already requested connection via this address! Vous avez déjà demandé une connexion via cette adresse ! @@ -7619,6 +8844,10 @@ Répéter la demande de connexion ? Vous avez envoyé une invitation de groupe No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! Vous serez connecté·e au groupe lorsque l'appareil de l'hôte sera en ligne, veuillez attendre ou vérifier plus tard ! @@ -7654,6 +8883,11 @@ Répéter la demande de connexion ? Vous continuerez à recevoir des appels et des notifications des profils mis en sourdine lorsqu'ils sont actifs. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + Vous ne recevrez plus de messages de cette discussion. L'historique sera préservé. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Vous ne recevrez plus de messages de ce groupe. L'historique du chat sera conservé. @@ -7674,31 +8908,16 @@ Répéter la demande de connexion ? Vous utilisez un profil incognito pour ce groupe - pour éviter de partager votre profil principal ; inviter des contacts n'est pas possible No comment provided by engineer. - - Your %@ servers - Vos serveurs %@ - No comment provided by engineer. - Your ICE servers Vos serveurs ICE No comment provided by engineer. - - Your SMP servers - Vos serveurs SMP - No comment provided by engineer. - Your SimpleX address Votre adresse SimpleX No comment provided by engineer. - - Your XFTP servers - Vos serveurs XFTP - No comment provided by engineer. - Your calls Vos appels @@ -7714,11 +8933,21 @@ Répéter la demande de connexion ? Votre base de données de chat n'est pas chiffrée - définisez une phrase secrète. No comment provided by engineer. + + Your chat preferences + Vos préférences de discussion + alert title + Your chat profiles Vos profils de chat No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@). @@ -7734,6 +8963,11 @@ Répéter la demande de connexion ? Vos contacts resteront connectés. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Vos informations d'identification peuvent être envoyées non chiffrées. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée. @@ -7764,33 +8998,36 @@ Répéter la demande de connexion ? Votre profil **%@** sera partagé. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. -Les serveurs SimpleX ne peuvent pas voir votre profil. + + Your profile is stored on your device and only shared with your contacts. + Le profil n'est partagé qu'avec vos contacts. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts. + alert message + Your random profile Votre profil aléatoire No comment provided by engineer. - - Your server - Votre serveur - No comment provided by engineer. - Your server address Votre adresse de serveur No comment provided by engineer. + + Your servers + Vos serveurs + No comment provided by engineer. + Your settings Vos paramètres @@ -7831,6 +9068,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. appel accepté call status + + accepted invitation + invitation acceptée + chat list item title + admin admin @@ -7866,6 +9108,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. et %lld autres événements No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts tentatives @@ -7904,7 +9150,8 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. blocked by admin bloqué par l'administrateur - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9266,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. connecting… connexion… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9321,8 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. default (%@) défaut (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9449,6 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. erreur No comment provided by engineer. - - event happened - event happened - No comment provided by engineer. - expired expiré @@ -8376,20 +9619,19 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. modéré par %@ marked deleted chat item preview text + + moderator + member role + months mois time unit - - mute - muet - No comment provided by engineer. - never jamais - No comment provided by engineer. + delete after time new message @@ -8420,8 +9662,8 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. off off enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9705,14 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. pair-à-pair No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption chiffrement e2e résistant post-quantique @@ -8478,6 +9728,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. confimation reçu… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call appel rejeté @@ -8508,6 +9762,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. vous a retiré rcv group event chat item + + requested to connect + demande à se connecter + chat list item title + saved enregistré @@ -8607,11 +9866,6 @@ dernier message reçu : %2$@ statut inconnu No comment provided by engineer. - - unmute - démuter - No comment provided by engineer. - unprotected non protégé @@ -8776,7 +10030,7 @@ dernier message reçu : %2$@
- +
@@ -8813,7 +10067,7 @@ dernier message reçu : %2$@
- +
@@ -8833,9 +10087,40 @@ dernier message reçu : %2$@
+ +
+ +
+ + + %d new events + %d nouveaux événements + notification body + + + From %d chat(s) + notification body + + + From: %@ + De : %@ + notification body + + + New events + Nouveaux événements + notification + + + New messages + Nouveaux messages + notification + + +
- +
@@ -8857,7 +10142,7 @@ dernier message reçu : %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/contents.json b/apps/ios/SimpleX Localizations/fr.xcloc/contents.json index 22d271b92e..d026c874ec 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/fr.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "fr", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 928a01dead..f76d7eba1e 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -217,23 +217,18 @@ Available in v5.1 ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **הוסיפו איש קשר חדש**: ליצירת קוד QR או קישור חד־פעמיים עבור איש הקשר שלכם. - No comment provided by engineer. - **Create link / QR code** for your contact to use. **צור קישור / קוד QR** לשימוש איש הקשר שלך. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **יותר פרטי**: בדוק הודעות חדשות כל 20 דקות. אסימון המכשיר משותף עם שרת SimpleX Chat, אך לא כמה אנשי קשר או הודעות יש לך. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **הכי פרטי**: אל תשתמש בשרת ההתראות של SimpleX Chat, בדוק הודעות מעת לעת ברקע (תלוי בתדירות השימוש באפליקציה). No comment provided by engineer. @@ -247,8 +242,8 @@ Available in v5.1 **שימו לב**: לא ניתן יהיה לשחזר או לשנות את הסיסמה אם תאבדו אותה. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **מומלץ**: אסימון מכשיר והתראות נשלחים לשרת ההתראות של SimpleX Chat, אך לא תוכן ההודעה, גודלה או ממי היא. No comment provided by engineer. @@ -1391,8 +1386,8 @@ Available in v5.1 הודעות ישירות chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. הודעות ישירות בין חברי קבוצה אסורות בקבוצה זו. No comment provided by engineer. @@ -1411,8 +1406,8 @@ Available in v5.1 הודעות נעלמות אסורות בצ׳אט זה. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. הודעות נעלמות אסורות בקבוצה זו. No comment provided by engineer. @@ -1956,18 +1951,18 @@ Available in v5.1 חברי הקבוצה יכולים למחוק באופן בלתי הפיך הודעות שנשלחו. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. חברי הקבוצה יכולים לשלוח הודעות ישירות. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. חברי הקבוצה יכולים לשלוח הודעות נעלמות. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. חברי הקבוצה יכולים לשלוח הודעות קוליות. No comment provided by engineer. @@ -2115,8 +2110,8 @@ Available in v5.1 מיד No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam חסין מפני ספאם ושימוש לרעה No comment provided by engineer. @@ -2257,8 +2252,8 @@ Available in v5.1 מחיקה בלתי הפיכה של הודעות אסורה בצ׳אט זה. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. מחיקה בלתי הפיכה של הודעות אסורה בקבוצה זו. No comment provided by engineer. @@ -2502,9 +2497,9 @@ Available in v5.1 ההעברה הושלמה No comment provided by engineer. - - Migrations: %@ - העברות: %@ + + Migrations: + העברות: No comment provided by engineer. @@ -2701,8 +2696,8 @@ Available in v5.1 לא ייעשה שימוש במארחי Onion. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2761,8 +2756,8 @@ Available in v5.1 Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2817,8 +2812,8 @@ Available in v5.1 Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -3521,8 +3516,8 @@ Available in v5.1 Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3566,16 +3561,16 @@ 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. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -3638,8 +3633,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3864,8 +3859,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -4005,10 +4000,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -4967,8 +4958,8 @@ SimpleX servers cannot see your profile. נמחק No comment provided by engineer. - - Files and media are prohibited in this group. + + Files and media are prohibited. קבצים ומדיה אסורים בקבוצה זו. No comment provided by engineer. @@ -5027,13 +5018,13 @@ SimpleX servers cannot see your profile. הזמן חברים No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. חברי הקבוצה יכולים להוסיף תגובות אמוג׳י להודעות. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. חברי הקבוצה יכולים לשלוח קבצים ומדיה. No comment provided by engineer. @@ -5231,8 +5222,8 @@ SimpleX servers cannot see your profile. תגובות אמוג׳י להודעות אסורות בצ׳אט זה. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. תגובות אמוג׳י להודעות אסורות בקבוצה זו. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index 50f5536e5e..6ad4d159c7 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -114,12 +114,12 @@ %lld - + No comment provided by engineer. %lld %@ - + No comment provided by engineer. @@ -144,12 +144,12 @@ %lldd - + No comment provided by engineer. %lldh - + No comment provided by engineer. @@ -158,7 +158,7 @@ %lldm - + No comment provided by engineer. @@ -173,17 +173,14 @@ %lldw No comment provided by engineer. - + ( + ( No comment provided by engineer. - + ) - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Dodajte novi kontakt**: da biste stvorili svoj jednokratni QR kôd ili vezu za svoj kontakt. + ) No comment provided by engineer. @@ -191,13 +188,13 @@ **Stvorite vezu / QR kôd** za vaš kontakt. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Privatnije**: provjeravajte nove poruke svakih 20 minuta. Token uređaja dijeli se s SimpleX Chat poslužiteljem, ali ne i s brojem kontakata ili poruka koje imate. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Najprivatniji**: nemojte koristiti SimpleX Chat poslužitelj obavijesti, povremeno provjeravajte poruke u pozadini (ovisi o tome koliko često koristite aplikaciju). No comment provided by engineer. @@ -211,8 +208,8 @@ **Imajte na umu**: NEĆETE moći oporaviti ili promijeniti pristupni izraz ako ga izgubite. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Preporučeno**: token uređaja i obavijesti šalju se na poslužitelj obavijesti SimpleX Chata, ali ne i sadržaj poruke, veličinu ili od koga je. No comment provided by engineer. @@ -253,22 +250,22 @@ 1 day - 1 dan + 1 dan message ttl 1 hour - 1 sat + 1 sat message ttl 1 month - 1 mjesec + 1 mesec message ttl 1 week - 1 tjedan + 1 nedelja message ttl @@ -1039,8 +1036,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1055,8 +1052,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1419,16 +1416,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1519,20 +1516,23 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. - + Import + Uvesti No comment provided by engineer. - + Import chat database? + Uvesti data bazu razgovora? No comment provided by engineer. - + Import database + Uvesti data bazu No comment provided by engineer. @@ -1616,8 +1616,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -1693,12 +1693,14 @@ We will be adding server redundancy to prevent lost messages. Live message! No comment provided by engineer. - + Live messages + Žive poruke No comment provided by engineer. - + Local name + Lokalno ime No comment provided by engineer. @@ -1917,8 +1919,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -1965,8 +1967,8 @@ We will be adding server redundancy to prevent lost messages. Open chat console authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -1997,8 +1999,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2577,8 +2579,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2609,16 +2611,16 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -2673,8 +2675,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2847,8 +2849,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -2959,10 +2961,6 @@ To connect, please ask your contact to create another connection link and check You can use markdown to format messages: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -3161,8 +3159,9 @@ SimpleX servers cannot see your profile. \_italic_ No comment provided by engineer. - + \`a + b` + \`a + b` No comment provided by engineer. @@ -3622,6 +3621,110 @@ SimpleX servers cannot see your profile. \~strike~ No comment provided by engineer. + + # %@ + # %@ + + + %@ server + %@ server + + + %@ servers + %@ serveri + + + Import failed + Uvoz neuspešan + + + %@ downloaded + %@ preuzeto + + + %@ uploaded + %@ otpremljeno + + + 1 minute + 1 minut + + + Password + Šifra + + + ## History + ## Istorija + + + %@ (current) + %@ (trenutan) + + + %@ and %@ + %@ i %@ + + + %@ connected + %@ povezan + + + 0 sec + 0 sek + + + 5 minutes + 5 minuta + + + %@ (current): + %@ (trenutan): + + + %@ and %@ connected + %@ i %@ su povezani + + + %@: + %@: + + + %1$@ at %2$@: + %1$@ u %2$@: + + + 30 seconds + 30 sekundi + + + Password to show + Prikazati šifru + + + %1$@, %2$@ + %1$@, %2$@ + + + 0s + 0s + + + Import theme + Uvesti temu + + + Immediately + Odmah + + + Address settings + Podešavanje adrese + + + Admins can block a member for all. + Administratori mogu da blokiraju +
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 f7328eed91..78bee138e4 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (másolható) @@ -54,12 +27,12 @@ ## In reply to - ## Válaszul erre: + ## Válaszul erre copied message info #secret# - #titkos# + #titok# No comment provided by engineer. @@ -99,7 +72,7 @@ %1$@ at %2$@: - %1$@ ekkor: %2$@ + %1$@ ekkor: %2$@: copied message info, <sender> at <time> @@ -119,12 +92,22 @@ %@ is not verified - %@ nem ellenőrzött + %@ nincs hitelesítve No comment provided by engineer. %@ is verified - %@ ellenőrizve + %@ hitelesítve + No comment provided by engineer. + + + %@ server + %@ kiszolgáló + No comment provided by engineer. + + + %@ servers + %@ kiszolgáló No comment provided by engineer. @@ -137,6 +120,11 @@ %@ kapcsolódni szeretne! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ és további %lld tag @@ -157,11 +145,36 @@ %d nap time interval + + %d file(s) are still being downloaded. + %d fájl letöltése még folyamatban van. + forward confirmation reason + + + %d file(s) failed to download. + Nem sikerült letölteni %d fájlt. + forward confirmation reason + + + %d file(s) were deleted. + %d fájl törölve lett. + forward confirmation reason + + + %d file(s) were not downloaded. + %d fájl nem lett letöltve. + forward confirmation reason + %d hours %d óra time interval + + %d messages not forwarded + %d üzenet nem lett továbbítva + alert title + %d min %d perc @@ -177,6 +190,11 @@ %d mp time interval + + %d seconds(s) + %d másodperc + delete after time + %d skipped message(s) %d üzenet kihagyva @@ -199,12 +217,12 @@ %lld contact(s) selected - %lld ismerős kiválasztva + %lld partner kijelölve No comment provided by engineer. %lld file(s) with total size of %@ - %lld fájl, amely(ek)nek teljes mérete: %@ + %lld fájl, %@ összméretben No comment provided by engineer. @@ -224,17 +242,17 @@ %lld messages blocked by admin - %lld üzenet letiltva az admin által + %lld üzenetet letiltott az adminisztrátor No comment provided by engineer. %lld messages marked deleted - %lld törlésre megjelölt üzenet + %lld üzenet megjelölve törlésre No comment provided by engineer. %lld messages moderated by %@ - %lld üzenet moderálva lett %@ által + %@ %lld üzenetet moderált No comment provided by engineer. @@ -244,27 +262,22 @@ %lld new interface languages - %lld új nyelvi csomag - No comment provided by engineer. - - - %lld second(s) - %lld másodperc + %lld új kezelőfelületi nyelv No comment provided by engineer. %lld seconds - %lld másodperc + %lld mp No comment provided by engineer. %lldd - %lldd + %lldn No comment provided by engineer. %lldh - %lldh + %lldó No comment provided by engineer. @@ -274,27 +287,27 @@ %lldm - %lldm + %lldp No comment provided by engineer. %lldmth - %lldmth + %lldh No comment provided by engineer. %llds - %llds + %lldmp No comment provided by engineer. %lldw - %lldw + %lldhét No comment provided by engineer. %u messages failed to decrypt. - %u üzenet visszafejtése sikertelen. + Nem sikerült visszafejteni %u üzenetet. No comment provided by engineer. @@ -302,11 +315,6 @@ %u üzenet kihagyva. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (új) @@ -317,59 +325,54 @@ (ez az eszköz: v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása az ismerőse számára. + + **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. No comment provided by engineer. **Create group**: to create a new group. - **Csoport létrehozása**: új csoport létrehozásához. + **Csoport létrehozása:** új csoport létrehozásához. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. + + **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 notifications server, check messages periodically in the background (depends 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. + **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). 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 az ismerőseitől érkező üzenetek visszafejtését. + **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. - **Figyelem**: NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt. + **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 notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. - **Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. + + **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. + 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. **Warning**: Instant push notifications require passphrase saved in Keychain. - **Figyelmeztetés**: Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges. + **Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges. No comment provided by engineer. **Warning**: the archive will be removed. - **Figyelem**: az archívum törlésre kerül. + **Figyelmeztetés:** az archívum el lesz távolítva. No comment provided by engineer. @@ -387,11 +390,6 @@ \*félkövér* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -406,7 +404,7 @@ - a bit better groups. - and more! - stabilabb üzenetkézbesítés. -- valamivel jobb csoportok. +- picit továbbfejlesztett csoportok. - és még sok más! No comment provided by engineer. @@ -414,8 +412,8 @@ - optionally notify deleted contacts. - profile names with spaces. - and more! - - opcionális értesítés a törölt kapcsolatokról. -- profilnevek szóközökkel. + - partnerek értesítése a törlésről (nem kötelező) +- profilnevek szóközökkel - és még sok más! No comment provided by engineer. @@ -423,16 +421,11 @@ - voice messages up to 5 minutes. - custom time to disappear. - editing history. - - hangüzenetek legfeljebb 5 perces időtartamig. -- egyedi eltűnési időhatár megadása. + - legfeljebb 5 perc hosszúságú hangüzenetek. +- egyéni üzenet-eltűnési időkorlát. - előzmények szerkesztése. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 mp @@ -446,7 +439,8 @@ 1 day 1 nap - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 hónap - time interval + delete after time +time interval 1 week 1 hét - time interval + delete after time +time interval + + + 1 year + 1 év + delete after time + + + 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ó. + No comment provided by engineer. 5 minutes @@ -483,43 +494,38 @@ 30 másodperc No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> <p>Üdvözlöm!</p> -<p><a href=„%@”>Csatlakozzon hozzám a SimpleX Chaten</a></p> +<p><a href="%@">Csatlakozzon hozzám a SimpleX Chaten keresztül</a></p> email text A few more things - Még néhány dolog + Néhány további dolog No comment provided by engineer. A new contact - Egy új ismerős + Egy új partner notification title A new random profile will be shared. - Egy új, véletlenszerű profil kerül megosztásra. + Egy új, véletlenszerű profil lesz megosztva. No comment provided by engineer. A separate TCP connection will be used **for each chat profile you have in the app**. - A rendszer külön TCP-kapcsolatot fog használni **az alkalmazásban található minden csevegési profilhoz**. + **Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. No comment provided by engineer. 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. - A rendszer külön TCP-kapcsolatot fog használni **minden ismerőshöz és csoporttaghoz**. -**Figyelem**: sok kapcsolódás esetén, az akkumulátor- és adatforgalom fogyasztás jelentősen megnőhet, és egyes kapcsolatok meghiúsulhatnak. + **Az összes partneréhez és csoporttaghoz** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. +**Megjegyzés:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. No comment provided by engineer. @@ -529,67 +535,77 @@ Abort changing address - Címváltoztatás megszakítása + Cím módosításának megszakítása No comment provided by engineer. Abort changing address? - Címváltoztatás megszakítása?? - No comment provided by engineer. - - - About SimpleX - A SimpleX-ről + Megszakítja a cím módosítását? No comment provided by engineer. About SimpleX Chat - A SimpleX Chat-ről + A SimpleX Chat névjegye No comment provided by engineer. - - About SimpleX address - A SimpleX címről + + About operators + Az üzemeltetőkről No comment provided by engineer. Accent - Kiemelés + Kiemelőszín No comment provided by engineer. Accept Elfogadás accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Feltételek elfogadása + No comment provided by engineer. Accept connection request? - Kapcsolódási kérelem elfogadása? + Elfogadja a meghívási kérést? No comment provided by engineer. Accept contact request from %@? - Elfogadja %@ kapcsolat kérését? + Elfogadja %@ meghívási kérését? notification body Accept incognito - Fogadás inkognítóban + Elfogadás inkognitóban accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Elfogadott feltételek + No comment provided by engineer. Acknowledged - Nyugtázva + Visszaigazolt No comment provided by engineer. Acknowledgement errors - Nyugtázott hibák + Visszaigazolási hibák No comment provided by engineer. + + Active + Aktív + token status text + Active connections Aktív kapcsolatok száma @@ -597,17 +613,17 @@ 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 az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára. + 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. No comment provided by engineer. - - Add contact - Ismerős hozzáadása + + Add friends + Barátok hozzáadása No comment provided by engineer. - - Add preset servers - Előre beállított kiszolgálók hozzáadása + + Add list + Lista hozzáadása No comment provided by engineer. @@ -625,29 +641,54 @@ Kiszolgáló hozzáadása QR-kód beolvasásával. No comment provided by engineer. + + Add team members + Munkatársak hozzáadása + No comment provided by engineer. + Add to another device Hozzáadás egy másik eszközhöz No comment provided by engineer. + + Add to list + Hozzáadás listához + No comment provided by engineer. + Add welcome message - Üdvözlő üzenet hozzáadása + Üdvözlőüzenet hozzáadása + No comment provided by engineer. + + + Add your team members to the conversations. + Adja hozzá a munkatársait a beszélgetésekhez. + No comment provided by engineer. + + + Added media & file servers + Hozzáadott média- és fájlkiszolgálók + No comment provided by engineer. + + + Added message servers + Hozzáadott üzenetkiszolgálók No comment provided by engineer. Additional accent - További kiemelés + További kiemelőszín No comment provided by engineer. Additional accent 2 - További kiemelés 2 + További kiemelőszín 2 No comment provided by engineer. Additional secondary - További másodlagos + További másodlagos szín No comment provided by engineer. @@ -657,17 +698,27 @@ Address change will be aborted. Old receiving address will be used. - A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. + A cím módosítása meg fog szakadni. A régi fogadási cím lesz használva. + No comment provided by engineer. + + + Address or 1-time link? + Cím vagy egyszer használható meghívó? + No comment provided by engineer. + + + Address settings + Címbeállítások No comment provided by engineer. Admins can block a member for all. - Az adminok egy tagot mindenki számára letilthatnak. + Az adminisztrátorok egy tagot a csoport összes tagja számára letilthatnak. No comment provided by engineer. Admins can create the links to join groups. - Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz. + Az adminisztrátorok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. No comment provided by engineer. @@ -677,67 +728,92 @@ Advanced settings - Haladó beállítások + Speciális beállítások + No comment provided by engineer. + + + All + Összes No comment provided by engineer. All app data is deleted. - Minden alkalmazásadat törölve. + Az összes alkalmazásadat törölve. No comment provided by engineer. All chats and messages will be deleted - this cannot be undone! - Minden csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! + Az összes csevegés és üzenet törölve lesz – ez a művelet nem vonható vissza! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Az összes csevegés el lesz távolítva a(z) %@ nevű listáról, és a lista is törölve lesz. + alert message + All data is erased when it is entered. - A jelkód megadása után minden adat törlésre kerül. + A jelkód megadása után az összes adat törölve lesz. No comment provided by engineer. - - All data is private to your device. - Minden adat biztonságban van a készülékén. + + All data is kept private on your device. + Az összes adat privát módon van tárolva az eszközén. No comment provided by engineer. All group members will remain connected. - Minden csoporttag kapcsolódva marad. + Az összes csoporttag kapcsolatban marad. + No comment provided by engineer. + + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Az összes üzenet és fájl **végpontok közötti titkosítással**, a közvetlen üzenetek továbbá kvantumbiztos titkosítással is rendelkeznek. No comment provided by engineer. All messages will be deleted - this cannot be undone! - Minden üzenet törlésre kerül – ez a művelet nem vonható vissza! + Az összes üzenet törölve lesz – ez a művelet nem vonható vissza! No comment provided by engineer. All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. - Minden üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az ön számára törlődnek. + Az összes üzenet törölve lesz – ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek. No comment provided by engineer. All new messages from %@ will be hidden! - Minden új üzenet elrejtésre kerül tőle: %@! + %@ összes új üzenete el lesz rejtve! No comment provided by engineer. All profiles - Minden profil + Összes profil + profile dropdown + + + All reports will be archived for you. + Az összes jelentés archiválva lesz az Ön számára. + No comment provided by engineer. + + + All servers + Összes kiszolgáló No comment provided by engineer. All your contacts will remain connected. - Minden ismerős kapcsolódva marad. + Az összes partnerével kapcsolatban marad. No comment provided by engineer. All your contacts will remain connected. Profile update will be sent to your contacts. - Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél. + A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára. No comment provided by engineer. All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. - Minden ismerőse, a beszélgetései és a fájljai biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP átjátszókra. + Az összes partnere, -beszélgetése és -fájlja biztonságosan titkosítva lesz, majd töredékekre bontva feltöltődnek a beállított XFTP-továbbítókiszolgálókra. No comment provided by engineer. @@ -747,17 +823,17 @@ Allow calls only if your contact allows them. - A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A hívások kezdeményezése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. No comment provided by engineer. Allow calls? - Hívások engedélyezése? + Engedélyezi a hívásokat? No comment provided by engineer. Allow disappearing messages only if your contact allows it to you. - Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi az ön számára. + Az eltűnő üzenetek küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi az Ön számára. No comment provided by engineer. @@ -767,17 +843,17 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) - Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) + 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 message reactions only if your contact allows them. - Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A reakciók hozzáadása az üzenetekhez csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. No comment provided by engineer. Allow message reactions. - Üzenetreakciók engedélyezése. + A reakciók hozzáadása az üzenetekhez engedélyezve van. No comment provided by engineer. @@ -797,57 +873,62 @@ Allow to irreversibly delete sent messages. (24 hours) - Elküldött üzenetek végleges törlésének engedélyezése. (24 óra) + Az elküldött üzenetek végleges törlése engedélyezve van. (24 óra) + No comment provided by engineer. + + + Allow to report messsages to moderators. + Az üzenetek jelentése a moderátorok felé engedélyezve van. No comment provided by engineer. Allow to send SimpleX links. - A SimpleX hivatkozások küldése engedélyezve van. + A SimpleX-hivatkozások küldése engedélyezve van. No comment provided by engineer. Allow to send files and media. - Fájlok és médiatartalom küldésének engedélyezése. + A fájlok- és a médiatartalmak küldése engedélyezve van. No comment provided by engineer. Allow to send voice messages. - Hangüzenetek küldésének engedélyezése. + A hangüzenetek küldése engedélyezve van. No comment provided by engineer. Allow voice messages only if your contact allows them. - A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. + A hangüzenetek küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. No comment provided by engineer. Allow voice messages? - Hangüzenetek engedélyezése? + Engedélyezi a hangüzeneteket? No comment provided by engineer. Allow your contacts adding message reactions. - Az üzenetreakciók küldése engedélyezve van az ismerősei számára. + A reakciók hozzáadása az üzenetekhez engedélyezve van a partnerei számára. No comment provided by engineer. Allow your contacts to call you. - A hívások kezdeményezése engedélyezve van az ismerősei számára. + A hívások kezdeményezése engedélyezve van a partnerei számára. No comment provided by engineer. Allow your contacts to irreversibly delete sent messages. (24 hours) - Az elküldött üzenetek végleges törlése engedélyezve van az ismerősei számára. (24 óra) + Az elküldött üzenetek végleges törlése engedélyezve van a partnerei számára. (24 óra) No comment provided by engineer. Allow your contacts to send disappearing messages. - Az eltűnő üzenetek küldésének engedélyezése az ismerősei számára. + Az eltűnő üzenetek küldésének engedélyezése a partnerei számára. No comment provided by engineer. Allow your contacts to send voice messages. - A hangüzenetek küldése engedélyezve van az ismerősei számára. + A hangüzenetek küldése engedélyezve van a partnerei számára. No comment provided by engineer. @@ -872,22 +953,32 @@ Always use relay - Mindig használjon átjátszó kiszolgálót + Mindig használjon továbbítókiszolgálót No comment provided by engineer. An empty chat profile with the provided name is created, and the app opens as usual. - Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik. + Egy üres csevegési profil lesz létrehozva a megadott névvel, és az alkalmazás a szokásos módon megnyílik. No comment provided by engineer. + + Another reason + Egyéb indoklás + report reason + 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: %@ - Az alkalmazás build száma: %@ + Az alkalmazás összeállítási száma: %@ No comment provided by engineer. @@ -900,9 +991,14 @@ Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével). No comment provided by engineer. + + App group: + Alkalmazáscsoport: + No comment provided by engineer. + App icon - Alkalmazás ikon + Alkalmazásikon No comment provided by engineer. @@ -912,17 +1008,22 @@ App passcode is replaced with self-destruct passcode. - Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal. + Az alkalmazás-jelkód helyettesítve lesz egy önmegsemmisítő-jelkóddal. + No comment provided by engineer. + + + App session + Alkalmazás munkamenete No comment provided by engineer. App version - Alkalmazás verzió + Az alkalmazás verziója No comment provided by engineer. App version: v%@ - Alkalmazás verzió: v%@ + Az alkalmazás verziója: v%@ No comment provided by engineer. @@ -940,6 +1041,21 @@ Alkalmazás erre No comment provided by engineer. + + Archive + Archívum + No comment provided by engineer. + + + Archive %lld reports? + Archivál %lld jelentést? + No comment provided by engineer. + + + Archive all reports? + Archiválja az összes jelentést? + No comment provided by engineer. + Archive and upload Archiválás és feltöltés @@ -947,12 +1063,27 @@ Archive contacts to chat later. - Ismerősök archiválása a későbbi csevegéshez. + A partnerek archiválása a későbbi csevegéshez. No comment provided by engineer. + + Archive report + Jelentés archiválása + No comment provided by engineer. + + + Archive report? + Archiválja a jelentést? + No comment provided by engineer. + + + Archive reports + Jelentések archiválása + swipe action + Archived contacts - Archivált ismerősök + Archivált partnerek No comment provided by engineer. @@ -962,7 +1093,7 @@ Attach - Csatolás + Mellékelés No comment provided by engineer. @@ -977,7 +1108,7 @@ Audio/video calls - Hang-/videóhívások + Hang- és videóhívások chat feature @@ -1012,7 +1143,7 @@ Auto-accept contact requests - Kapcsolódási kérelmek automatikus elfogadása + Meghívási kérések automatikus elfogadása No comment provided by engineer. @@ -1020,6 +1151,11 @@ Képek automatikus elfogadása No comment provided by engineer. + + Auto-accept settings + Beállítások automatikus elfogadása + alert title + Back Vissza @@ -1032,7 +1168,7 @@ Bad desktop address - Hibás számítógép cím + Érvénytelen számítógépcím No comment provided by engineer. @@ -1042,17 +1178,32 @@ Bad message hash - Hibás az üzenet ellenőrzőösszege + Érvénytelen az üzenet hasítóértéke + No comment provided by engineer. + + + Better calls + Továbbfejlesztett hívásélmény No comment provided by engineer. Better groups - Javított csoportok + Továbbfejlesztett csoportok + No comment provided by engineer. + + + Better groups performance + Továbbfejlesztett, gyorsabb csoportok + No comment provided by engineer. + + + Better message dates. + Továbbfejlesztett üzenetdátumok. No comment provided by engineer. Better messages - Jobb üzenetek + Továbbfejlesztett üzenetek No comment provided by engineer. @@ -1060,6 +1211,26 @@ Jobb hálózatkezelés No comment provided by engineer. + + Better notifications + Továbbfejlesztett értesítések + No comment provided by engineer. + + + Better privacy and security + Továbbfejlesztett adatvédelem és biztonság + No comment provided by engineer. + + + Better security ✅ + Továbbfejlesztett biztonság ✅ + No comment provided by engineer. + + + Better user experience + Továbbfejlesztett felhasználói élmény + No comment provided by engineer. + Black Fekete @@ -1072,7 +1243,7 @@ Block for all - Letiltás mindenki számára + Letiltás No comment provided by engineer. @@ -1082,22 +1253,22 @@ Block member - Tag letiltása + Letiltás No comment provided by engineer. Block member for all? - Mindenki számára letiltja ezt a tagot? + Az összes tag számára letiltja a tagot? No comment provided by engineer. Block member? - Tag letiltása? + Letiltja a tagot? No comment provided by engineer. Blocked by admin - Letiltva az admin által + Letiltva az adminisztrátor által No comment provided by engineer. @@ -1107,17 +1278,17 @@ Blur media - Média elhomályosítása + Médiatartalom elhomályosítása No comment provided by engineer. Both you and your contact can add message reactions. - Mindkét fél is hozzáadhat üzenetreakciókat. + Mindkét fél hozzáadhat az üzenetekhez reakciókat. No comment provided by engineer. Both you and your contact can irreversibly delete sent messages. (24 hours) - Mindkét fél törölheti véglegesen az elküldött üzeneteket. (24 óra) + Mindkét fél véglegesen törölheti az elküldött üzeneteket. (24 óra) No comment provided by engineer. @@ -1140,9 +1311,33 @@ 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)! No comment provided by engineer. + + Business address + Üzleti cím + No comment provided by engineer. + + + Business chats + Üzleti csevegések + No comment provided by engineer. + + + Businesses + Üzleti + 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). - Csevegési profil (alapértelmezett) vagy [kapcsolat alapján] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). + 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. @@ -1167,7 +1362,7 @@ Can't call contact - Nem lehet felhívni az ismerőst + Nem lehet felhívni a partnert No comment provided by engineer. @@ -1177,12 +1372,12 @@ Can't invite contact! - Ismerősök meghívása le van tiltva! + Nem lehet meghívni a partnert! No comment provided by engineer. Can't invite contacts! - Ismerősök meghívása nem lehetséges! + Nem lehet meghívni a partnereket! No comment provided by engineer. @@ -1193,7 +1388,8 @@ Cancel Mégse - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1202,7 +1398,7 @@ Cannot access keychain to save database password - Nem lehet hozzáférni a kulcstartóhoz az adatbázis jelszavának mentéséhez + Nem lehet hozzáférni a kulcstartóhoz az adatbázisjelszó mentéséhez No comment provided by engineer. @@ -1213,11 +1409,11 @@ Cannot receive file Nem lehet fogadni a fájlt - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. - Kapacitás túllépés - a címzett nem kapta meg a korábban elküldött üzeneteket. + Kapacitás túllépés – a címzett nem kapta meg a korábban elküldött üzeneteket. snd error text @@ -1227,58 +1423,78 @@ Change - Változtatás + Módosítás No comment provided by engineer. + + Change automatic message deletion? + Módosítja az automatikus üzenettörlést? + alert title + + + Change chat profiles + Csevegési profilok módosítása + authentication reason + Change database passphrase? - Adatbázis jelmondat megváltoztatása? + Módosítja az adatbázis jelmondatát? No comment provided by engineer. Change lock mode - Zárolási mód megváltoztatása + Zárolási mód módosítása authentication reason Change member role? - Tag szerepkörének megváltoztatása? + Módosítja a tag szerepkörét? No comment provided by engineer. Change passcode - Jelkód megváltoztatása + Jelkód módosítása authentication reason Change receiving address - A fogadó cím megváltoztatása + Fogadási cím módosítása No comment provided by engineer. Change receiving address? - Megváltoztatja a fogadó címet? + Módosítja a fogadási címet? No comment provided by engineer. Change role - Szerepkör megváltoztatása + Szerepkör módosítása No comment provided by engineer. Change self-destruct mode - Önmegsemmisítő mód megváltoztatása + Önmegsemmisítő-mód módosítása authentication reason Change self-destruct passcode - Önmegsemmisító jelkód megváltoztatása + Önmegsemmisítő-jelkód módosítása authentication reason - set passcode view +set passcode view - - Chat archive - Csevegési archívum + + Chat + Csevegés + No comment provided by engineer. + + + Chat already exists + A csevegés már létezik + No comment provided by engineer. + + + Chat already exists! + A csevegés már létezik! No comment provided by engineer. @@ -1318,17 +1534,17 @@ Chat is stopped - A csevegés leállt + A csevegés megállt 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. - A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt. + A csevegés megállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés elindítása előtt. No comment provided by engineer. Chat list - Csevegőlista + Csevegési lista No comment provided by engineer. @@ -1341,20 +1557,50 @@ Csevegési beállítások No comment provided by engineer. + + Chat preferences were changed. + A csevegési beállítások módosultak. + alert message + + + Chat profile + Csevegési profil + No comment provided by engineer. + Chat theme Csevegés témája No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + A csevegés minden tag számára törölve lesz – ez a művelet nem vonható vissza! + 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! + No comment provided by engineer. + Chats Csevegések 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. + No comment provided by engineer. + Check server address and try again. Kiszolgáló címének ellenőrzése és újrapróbálkozás. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1363,7 +1609,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. - Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközön és olvassa be a QR-kódot. + Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot. No comment provided by engineer. @@ -1378,17 +1624,17 @@ Chunks deleted - Törölt fájltöredékek + Törölt töredékek No comment provided by engineer. Chunks downloaded - Letöltött fájltöredékek + Letöltött töredékek No comment provided by engineer. Chunks uploaded - Feltöltött fájltöredékek + Feltöltött töredékek No comment provided by engineer. @@ -1403,12 +1649,22 @@ Clear conversation? - Üzenetek kiürítése? + Kiüríti az üzeneteket? + No comment provided by engineer. + + + Clear group? + Kiüríti a csoportot? + No comment provided by engineer. + + + Clear or delete group? + Csoport kiürítése vagy törlése? No comment provided by engineer. Clear private notes? - Privát jegyzetek kiürítése? + Kiüríti a privát jegyzeteket? No comment provided by engineer. @@ -1426,14 +1682,19 @@ Színmód No comment provided by engineer. + + Community guidelines violation + Közösségi irányelvek megsértése + report reason + Compare file - Fájl összehasonlítás + Fájl-összehasonlítás server test step Compare security codes with your contacts. - Biztonsági kódok összehasonlítása az ismerősökkel. + Biztonsági kódok összehasonlítása a partnerekével. No comment provided by engineer. @@ -1441,14 +1702,49 @@ Elkészült No comment provided by engineer. - - Configure ICE servers - ICE kiszolgálók beállítása + + Conditions accepted on: %@. + Feltételek elfogadásának ideje: %@. No comment provided by engineer. - - Configured %@ servers - Beállított %@ kiszolgálók + + Conditions are accepted for the operator(s): **%@**. + A következő üzemeltető(k) számára elfogadott feltételek: **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + A feltételek már el lettek fogadva a következő üzemeltető(k) számára: **%@**. + No comment provided by engineer. + + + Conditions of use + Használati feltételek + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + A feltételek el lesznek fogadva a következő időpontban: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + A feltételek automatikusan el lesznek fogadva az engedélyezett üzemeltetők számára a következő időpontban: %@. + No comment provided by engineer. + + + 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 No comment provided by engineer. @@ -1463,7 +1759,7 @@ Confirm contact deletion? - Biztosan törli az ismerőst? + Biztosan törli a partnert? No comment provided by engineer. @@ -1473,7 +1769,7 @@ Confirm files from unknown servers. - Ismeretlen kiszolgálókról származó fájlok jóváhagyása. + Ismeretlen kiszolgálókról származó fájlok megerősítése. No comment provided by engineer. @@ -1493,7 +1789,7 @@ Confirm that you remember database passphrase to migrate it. - Erősítse meg, hogy emlékszik az adatbázis jelmondatára az átköltöztetéshez. + Az átköltöztetéshez erősítse meg, hogy emlékszik az adatbázis jelmondatára. No comment provided by engineer. @@ -1501,6 +1797,11 @@ Feltöltés megerősítése No comment provided by engineer. + + Confirmed + Megerősítve + token status text + Connect Kapcsolódás @@ -1518,31 +1819,31 @@ Connect to desktop - Kapcsolódás számítógéphez + Társítás számítógéppel No comment provided by engineer. Connect to your friends faster. - Kapcsolódjon gyorsabban az ismerőseihez. + Kapcsolódjon gyorsabban a partnereihez. No comment provided by engineer. Connect to yourself? - Kapcsolódás saját magához? + Kapcsolódik saját magához? No comment provided by engineer. Connect to yourself? This is your own SimpleX address! - Kapcsolódás saját magához? -Ez az ön SimpleX címe! + Kapcsolódik saját magához? +Ez a saját SimpleX-címe! No comment provided by engineer. Connect to yourself? This is your own one-time link! - Kapcsolódás saját magához? -Ez az egyszer használatos hivatkozása! + Kapcsolódik saját magához? +Ez a saját egyszer használható meghívója! No comment provided by engineer. @@ -1557,22 +1858,22 @@ Ez az egyszer használatos hivatkozása! Connect via one-time link - Kapcsolódás egyszer használatos hivatkozáson keresztül + Kapcsolódás egyszer használható meghívón keresztül No comment provided by engineer. Connect with %@ - Kapcsolódás ezzel: %@ + Kapcsolódás a következővel: %@ No comment provided by engineer. Connected - Kapcsolódva + Kapcsolódott No comment provided by engineer. Connected desktop - Csatlakoztatott számítógép + Társított számítógép No comment provided by engineer. @@ -1597,12 +1898,12 @@ Ez az egyszer használatos hivatkozása! Connecting to server… (error: %@) - Kapcsolódás a kiszolgálóhoz... (hiba: %@) + Kapcsolódás a kiszolgálóhoz… (hiba: %@) No comment provided by engineer. Connecting to contact, please wait or check later! - Kapcsolódás az ismerőshöz, várjon vagy ellenőrizze később! + Kapcsolódás a partnerhez, várjon vagy ellenőrizze később! No comment provided by engineer. @@ -1620,6 +1921,11 @@ Ez az egyszer használatos hivatkozása! Kapcsolatok- és kiszolgálók állapotának megjelenítése. No comment provided by engineer. + + Connection blocked + A kapcsolat le van tiltva + No comment provided by engineer. + Connection error Kapcsolódási hiba @@ -1630,6 +1936,18 @@ Ez az egyszer használatos hivatkozása! Kapcsolódási hiba (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + A kiszolgáló üzemeltetője letiltotta a kapcsolatot: +%@ + No comment provided by engineer. + + + Connection not ready. + A kapcsolat nem áll készen. + No comment provided by engineer. + Connection notifications Kapcsolódási értesítések @@ -1637,7 +1955,17 @@ Ez az egyszer használatos hivatkozása! Connection request sent! - Kapcsolódási kérés elküldve! + Meghívási kérés elküldve! + No comment provided by engineer. + + + Connection requires encryption renegotiation. + A kapcsolat titkosítása újraegyeztetést igényel. + No comment provided by engineer. + + + Connection security + Kapcsolatbiztonság No comment provided by engineer. @@ -1647,7 +1975,7 @@ Ez az egyszer használatos hivatkozása! Connection timeout - Kapcsolat időtúllépés + Időtúllépés kapcsolódáskor No comment provided by engineer. @@ -1662,59 +1990,64 @@ Ez az egyszer használatos hivatkozása! Contact allows - Ismerős engedélyezi + Partner engedélyezi No comment provided by engineer. Contact already exists - Létező ismerős + A partner már létezik No comment provided by engineer. Contact deleted! - Ismerős törölve! + Partner törölve! No comment provided by engineer. Contact hidden: - Ismerős elrejtve: + Rejtett név: notification Contact is connected - Ismerőse kapcsolódott + Partnere kapcsolódott notification Contact is deleted. - Törölt ismerős. + Törölt partner. No comment provided by engineer. Contact name - Ismerős neve + Csak név No comment provided by engineer. Contact preferences - Ismerős beállításai + Partnerbeállítások No comment provided by engineer. Contact will be deleted - this cannot be undone! - Az ismerős törlésre fog kerülni - ez a művelet nem vonható vissza! + A partner törölve lesz – ez a művelet nem vonható vissza! No comment provided by engineer. Contacts - Ismerősök + Partnerek No comment provided by engineer. Contacts can mark messages for deletion; you will be able to view them. - Az ismerősei törlésre jelölhetnek üzeneteket; ön majd meg tudja nézni azokat. + A partnerei törlésre jelölhetnek üzeneteket; Ön majd meg tudja nézni azokat. No comment provided by engineer. + + Content violates conditions of use + A tartalom sérti a használati feltételeket + blocking reason + Continue Folytatás @@ -1737,12 +2070,17 @@ Ez az egyszer használatos hivatkozása! Core version: v%@ - Alapverziószám: v%@ + Fő verzió: v%@ + No comment provided by engineer. + + + Corner + Sarok No comment provided by engineer. Correct name to %@? - Név javítása erre: %@? + Helyesbíti a nevet a következőre: %@? No comment provided by engineer. @@ -1750,19 +2088,19 @@ Ez az egyszer használatos hivatkozása! Létrehozás No comment provided by engineer. + + Create 1-time link + Egyszer használható meghívó létrehozása + No comment provided by engineer. + Create SimpleX address - SimpleX cím létrehozása + SimpleX-cím létrehozása No comment provided by engineer. Create a group using a random profile. - Csoport létrehozása véletlenszerűen létrehozott profillal. - No comment provided by engineer. - - - Create an address to let people connect with you. - Cím létrehozása, hogy az emberek kapcsolatba léphessenek önnel. + Csoport létrehozása véletlenszerű profillal. No comment provided by engineer. @@ -1777,7 +2115,7 @@ Ez az egyszer használatos hivatkozása! Create group link - Csoportos hivatkozás létrehozása + Csoporthivatkozás létrehozása No comment provided by engineer. @@ -1785,9 +2123,14 @@ Ez az egyszer használatos hivatkozása! Hivatkozás létrehozása No comment provided by engineer. + + Create list + Lista létrehozása + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 - Új profil létrehozása az [asztali kliensben](https://simplex.chat/downloads/). 💻 + Új profil létrehozása a [számítógép-alkalmazásban](https://simplex.chat/downloads/). 💻 No comment provided by engineer. @@ -1797,7 +2140,7 @@ Ez az egyszer használatos hivatkozása! Create queue - Várólista létrehozása + Sorba állítás létrehozása server test step @@ -1817,22 +2160,17 @@ Ez az egyszer használatos hivatkozása! Created at - Létrehozva ekkor: + Létrehozva No comment provided by engineer. Created at: %@ - Létrehozva ekkor: %@ + Létrehozva: %@ copied message info - - Created on %@ - Létrehozva %@ - No comment provided by engineer. - Creating archive link - Archív hivatkozás létrehozása + Archívum hivatkozás létrehozása No comment provided by engineer. @@ -1845,6 +2183,11 @@ Ez az egyszer használatos hivatkozása! Jelenlegi jelkód No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + A jelenlegi feltételek szövegét nem lehetett betölteni, a feltételeket a következő hivatkozáson keresztül vizsgálhatja felül: + No comment provided by engineer. + Current passphrase… Jelenlegi jelmondat… @@ -1857,12 +2200,17 @@ Ez az egyszer használatos hivatkozása! Currently maximum supported file size is %@. - Jelenleg a maximális támogatott fájlméret %@. + Jelenleg támogatott legnagyobb fájl méret: %@. No comment provided by engineer. Custom time - Személyreszabott idő + Egyéni időköz + No comment provided by engineer. + + + Customizable message shape. + Személyre szabható üzenetbuborékok. No comment provided by engineer. @@ -1882,17 +2230,17 @@ Ez az egyszer használatos hivatkozása! Database ID - Adatbázis ID + Adatbázis-azonosító No comment provided by engineer. Database ID: %d - Adatbázis azonosító: %d + Adatbázis-azonosító: %d copied message info Database IDs and Transport isolation option. - Adatbázis azonosítók és átviteli izolációs beállítások. + Adatbázis-azonosítók és átvitel-izolációs beállítások. No comment provided by engineer. @@ -1908,45 +2256,45 @@ Ez az egyszer használatos hivatkozása! Database encryption passphrase will be updated and stored in the keychain. - Az adatbázis titkosítási jelmondata frissül és tárolódik a kulcstartóban. + Az adatbázis titkosítási jelmondata frissülni fog és a kulcstartóban lesz tárolva. No comment provided by engineer. Database encryption passphrase will be updated. - Adatbázis titkosítási jelmondat frissítve lesz. + Az adatbázis titkosítási jelmondata frissítve lesz. No comment provided by engineer. Database error - Adatbázis hiba + Adatbázishiba No comment provided by engineer. Database is encrypted using a random passphrase, you can change it. - Az adatbázis egy véletlenszerű jelmondattal van titkosítva, megváltoztatható. + Az adatbázis egy véletlenszerű jelmondattal van titkosítva, amelyet szabadon módosíthat. No comment provided by engineer. Database is encrypted using a random passphrase. Please change it before exporting. - Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtti módosítás szükséges. + Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtt módosítsa. No comment provided by engineer. Database passphrase - Adatbázis jelmondat + Adatbázis-jelmondat No comment provided by engineer. Database passphrase & export - Adatbázis jelmondat és exportálás + Adatbázis-jelmondat és -exportálás No comment provided by engineer. Database passphrase is different from saved in the keychain. - Az adatbázis jelmondata eltér a kulcstartóban mentettől. + Az adatbázis jelmondata nem egyezik a kulcstartóba mentettől. No comment provided by engineer. @@ -1969,13 +2317,13 @@ Ez az egyszer használatos hivatkozása! Database will be encrypted. - Az adatbázis titkosításra kerül. + Az adatbázis titkosítva lesz. No comment provided by engineer. Database will be migrated when the app restarts - Az adatbázis az alkalmazás újraindításakor migrálásra kerül + Az adatbázis az alkalmazás újraindításakor lesz átköltöztetve No comment provided by engineer. @@ -1996,12 +2344,12 @@ Ez az egyszer használatos hivatkozása! Delete Törlés - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? - Tagok %lld üzenetének törlése? + Törli a tagok %lld üzenetét? No comment provided by engineer. @@ -2016,7 +2364,7 @@ Ez az egyszer használatos hivatkozása! Delete address? - Cím törlése? + Törli a címet? No comment provided by engineer. @@ -2026,22 +2374,22 @@ Ez az egyszer használatos hivatkozása! Delete all files - Minden fájl törlése + Az összes fájl törlése No comment provided by engineer. Delete and notify contact - Törlés, és az ismerős értesítése + Törlés, és a partner értesítése No comment provided by engineer. - - Delete archive - Archívum törlése + + Delete chat + Csevegés törlése No comment provided by engineer. - - Delete chat archive? - Csevegési archívum törlése? + + Delete chat messages from your device. + Csevegési üzenetek törlése a saját eszközéről. No comment provided by engineer. @@ -2051,7 +2399,12 @@ Ez az egyszer használatos hivatkozása! Delete chat profile? - Csevegési profil törlése? + Törli a csevegési profilt? + No comment provided by engineer. + + + Delete chat? + Törli a csevegést? No comment provided by engineer. @@ -2061,12 +2414,12 @@ Ez az egyszer használatos hivatkozása! Delete contact - Ismerős törlése + Partner törlése No comment provided by engineer. Delete contact? - Ismerős törlése? + Törli a partnert? No comment provided by engineer. @@ -2086,17 +2439,17 @@ Ez az egyszer használatos hivatkozása! Delete files and media? - Fájlok és a médiatartalmak törlése? + Törli a fájl- és a médiatartalmakat? No comment provided by engineer. Delete files for all chat profiles - Fájlok törlése minden csevegési profilból + Fájlok törlése az összes csevegési profilból No comment provided by engineer. Delete for everyone - Törlés mindenkinél + Törlés az összes tagnál chat feature @@ -2111,7 +2464,7 @@ Ez az egyszer használatos hivatkozása! Delete group? - Csoport törlése? + Törli a csoportot? No comment provided by engineer. @@ -2121,28 +2474,33 @@ Ez az egyszer használatos hivatkozása! Delete link - Hivatkozás törlése + Törlés No comment provided by engineer. Delete link? - Hivatkozás törlése? + Törli a hivatkozást? No comment provided by engineer. + + Delete list? + Törli a listát? + alert title + Delete member message? - Csoporttag üzenetének törlése? + Törli a tag üzenetét? No comment provided by engineer. Delete message? - Üzenet törlése? + Törli az üzenetet? No comment provided by engineer. Delete messages Üzenetek törlése - No comment provided by engineer. + alert button Delete messages after @@ -2156,12 +2514,17 @@ Ez az egyszer használatos hivatkozása! Delete old database? - Régi adatbázis törlése? + Törli a régi adatbázist? + No comment provided by engineer. + + + Delete or moderate up to 200 messages. + Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása. No comment provided by engineer. Delete pending connection? - Függő kapcsolatfelvételi kérések törlése? + Törli a függőben lévő meghívót? No comment provided by engineer. @@ -2171,17 +2534,22 @@ Ez az egyszer használatos hivatkozása! Delete queue - Várólista törlése + Sorba állítás törlése server test step + + Delete report + Jelentés törlése + No comment provided by engineer. + Delete up to 20 messages at once. - Legfeljebb 20 üzenet törlése egyszerre. + Legfeljebb 20 üzenet egyszerre való törlése. No comment provided by engineer. Delete user profile? - Felhasználói profil törlése? + Törli a felhasználói profilt? No comment provided by engineer. @@ -2196,12 +2564,12 @@ Ez az egyszer használatos hivatkozása! Deleted at - Törölve ekkor: + Törölve No comment provided by engineer. Deleted at: %@ - Törölve ekkor: %@ + Törölve: %@ copied message info @@ -2209,6 +2577,11 @@ Ez az egyszer használatos hivatkozása! Törlési hibák No comment provided by engineer. + + Delivered even when Apple drops them. + Kézbesítés akkor is, amikor az Apple eldobja őket. + No comment provided by engineer. + Delivery Kézbesítés @@ -2216,12 +2589,12 @@ Ez az egyszer használatos hivatkozása! Delivery receipts are disabled! - A kézbesítési jelentések ki vannak kapcsolva! + A kézbesítési jelentések le vannak tiltva! No comment provided by engineer. Delivery receipts! - Üzenet kézbesítési jelentések! + Kézbesítési jelentések! No comment provided by engineer. @@ -2236,7 +2609,7 @@ Ez az egyszer használatos hivatkozása! Desktop app version %@ is not compatible with this app. - Az asztali kliens verziója %@ nem kompatibilis ezzel az alkalmazással. + A számítógép-alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással. No comment provided by engineer. @@ -2246,17 +2619,17 @@ Ez az egyszer használatos hivatkozása! Destination server address of %@ is incompatible with forwarding server %@ settings. - A(z) %@ célkiszolgáló címe nem kompatibilis a(z) %@ továbbító kiszolgáló beállításaival. + A(z) %@ célkiszolgáló címe nem kompatibilis a(z) %@ továbbítókiszolgáló beállításaival. No comment provided by engineer. Destination server error: %@ - Célkiszolgáló hiba: %@ + Célkiszolgáló-hiba: %@ snd error text Destination server version of %@ is incompatible with forwarding server %@. - A(z) %@ célkiszolgáló verziója nem kompatibilis a(z) %@ továbbító kiszolgálóval. + A(z) %@ célkiszolgáló verziója nem kompatibilis a(z) %@ továbbítókiszolgálóval. No comment provided by engineer. @@ -2266,7 +2639,7 @@ Ez az egyszer használatos hivatkozása! Details - Részletek + További részletek No comment provided by engineer. @@ -2291,17 +2664,17 @@ Ez az egyszer használatos hivatkozása! Device authentication is disabled. Turning off SimpleX Lock. - A készüléken nincs beállítva a képernyőzár. A SimpleX zár ki van kapcsolva. + Az eszközön nincs beállítva a képernyőzár. A SimpleX-zár ki van kapcsolva. No comment provided by engineer. Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication. - A készüléken nincs beállítva a képernyőzár. A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. + Az eszközön nincs beállítva a képernyőzár. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. No comment provided by engineer. Different names, avatars and transport isolation. - Különböző nevek, avatarok és átviteli izoláció. + Különböző nevek, profilképek és átvitel-izoláció. No comment provided by engineer. @@ -2309,9 +2682,14 @@ Ez az egyszer használatos hivatkozása! Közvetlen üzenetek chat feature - - Direct messages between members are prohibited in this group. - A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. + + Direct messages between members are prohibited in this chat. + A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben. + 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. @@ -2321,12 +2699,22 @@ Ez az egyszer használatos hivatkozása! Disable SimpleX Lock - SimpleX zár kikapcsolása + SimpleX-zár kikapcsolása authentication reason + + Disable automatic message deletion? + Letiltja az automatikus üzenettörlést? + alert title + + + Disable delete messages + Üzenetek törlésének letiltása + alert button + Disable for all - Letiltás mindenki számára + Letiltás No comment provided by engineer. @@ -2349,19 +2737,19 @@ Ez az egyszer használatos hivatkozása! Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Disappearing messages are prohibited in this group. - Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. + + Disappearing messages are prohibited. + Az eltűnő üzenetek küldése le van tiltva. No comment provided by engineer. Disappears at - Eltűnik ekkor: + Eltűnik No comment provided by engineer. Disappears at: %@ - Eltűnik ekkor: %@ + Eltűnik: %@ copied message info @@ -2371,12 +2759,12 @@ Ez az egyszer használatos hivatkozása! Disconnect desktop? - Számítógép leválasztása? + Leválasztja a számítógépet? No comment provided by engineer. Discover and join groups - Helyi csoportok felfedezése és csatlakozás + Csoportok felfedezése és csatlakozás No comment provided by engineer. @@ -2386,27 +2774,37 @@ Ez az egyszer használatos hivatkozása! 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 az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + 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. No comment provided by engineer. Do NOT use SimpleX for emergency calls. - NE használja a SimpleX-et segélyhívásokhoz. + NE használja a SimpleXet segélyhívásokhoz. No comment provided by engineer. Do NOT use private routing. - Ne használjon privát útválasztást. + NE használjon privát útválasztást. No comment provided by engineer. Do it later - Későbbre halaszt + Befejezés később No comment provided by engineer. Do not send history to new members. - Az előzmények ne kerüljenek elküldésre az új tagok számára. + Az előzmények ne legyenek elküldve az új tagok számára. + No comment provided by engineer. + + + Do not use credentials with proxy. + Ne használja a hitelesítőadatokat proxyval. + No comment provided by engineer. + + + Documents: + Dokumentumok: No comment provided by engineer. @@ -2419,11 +2817,21 @@ Ez az egyszer használatos hivatkozása! Ne engedélyezze No comment provided by engineer. + + Don't miss important messages. + Ne maradjon le a fontos üzenetekről. + No comment provided by engineer. + Don't show again Ne mutasd újra No comment provided by engineer. + + Done + Kész + No comment provided by engineer. + Downgrade and open chat Visszafejlesztés és a csevegés megnyitása @@ -2432,7 +2840,8 @@ Ez az egyszer használatos hivatkozása! Download Letöltés - chat item action + alert button +chat item action Download errors @@ -2449,6 +2858,11 @@ Ez az egyszer használatos hivatkozása! Fájl letöltése server test step + + Download files + Fájlok letöltése + alert action + Downloaded Letöltve @@ -2471,7 +2885,7 @@ Ez az egyszer használatos hivatkozása! Duplicate display name! - Duplikált megjelenítési név! + Duplikált megjelenítendő név! No comment provided by engineer. @@ -2479,6 +2893,11 @@ Ez az egyszer használatos hivatkozása! Időtartam No comment provided by engineer. + + E2E encrypted notifications. + Végpontok közötti titkosított értesítések. + No comment provided by engineer. + Edit Szerkesztés @@ -2486,7 +2905,7 @@ Ez az egyszer használatos hivatkozása! Edit group profile - A csoport profiljának szerkesztése + Csoportprofil szerkesztése No comment provided by engineer. @@ -2499,9 +2918,14 @@ Ez az egyszer használatos hivatkozása! Engedélyezés (felülírások megtartásával) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a metaadatok jobb védelme érdekében. + No comment provided by engineer. + Enable SimpleX Lock - SimpleX zár bekapcsolása + SimpleX-zár bekapcsolása authentication reason @@ -2511,8 +2935,8 @@ Ez az egyszer használatos hivatkozása! Enable automatic message deletion? - Automatikus üzenet törlés engedélyezése? - No comment provided by engineer. + Engedélyezi az automatikus üzenettörlést? + alert title Enable camera access @@ -2521,7 +2945,7 @@ Ez az egyszer használatos hivatkozása! Enable for all - Engedélyezés mindenki számára + Engedélyezés az összes tag számára No comment provided by engineer. @@ -2531,7 +2955,7 @@ Ez az egyszer használatos hivatkozása! Enable instant notifications? - Azonnali értesítések engedélyezése? + Engedélyezi az azonnali értesítéseket? No comment provided by engineer. @@ -2546,7 +2970,7 @@ Ez az egyszer használatos hivatkozása! Enable periodic notifications? - Időszakos értesítések engedélyezése? + Engedélyezi az időszakos értesítéseket? No comment provided by engineer. @@ -2556,7 +2980,7 @@ Ez az egyszer használatos hivatkozása! Enable self-destruct passcode - Önmegsemmisítő jelkód engedélyezése + Önmegsemmisítő-jelkód engedélyezése set passcode view @@ -2566,7 +2990,7 @@ Ez az egyszer használatos hivatkozása! Enabled for - Engedélyezve + Számukra engedélyezve No comment provided by engineer. @@ -2576,7 +3000,7 @@ Ez az egyszer használatos hivatkozása! Encrypt database? - Adatbázis titkosítása? + Titkosítja az adatbázist? No comment provided by engineer. @@ -2586,7 +3010,7 @@ Ez az egyszer használatos hivatkozása! Encrypt stored files & media - Tárolt fájlok és médiatartalmak titkosítása + A tárolt fájlok- és a médiatartalmak titkosítása No comment provided by engineer. @@ -2601,17 +3025,17 @@ Ez az egyszer használatos hivatkozása! Encrypted message: app is stopped - Titkosított üzenet: az alkalmazás leállt + Titkosított üzenet: az alkalmazás megállt notification Encrypted message: database error - Titkosított üzenet: adatbázis hiba + Titkosított üzenet: adatbázishiba notification Encrypted message: database migration error - Titkosított üzenet: adatbázis-átköltöztetés hiba + Titkosított üzenet: adatbázis-átköltöztetési hiba notification @@ -2631,62 +3055,67 @@ Ez az egyszer használatos hivatkozása! Encryption re-negotiation error - Titkosítás újraegyeztetési hiba + Hiba történt a titkosítás újraegyeztetésekor message decrypt error item Encryption re-negotiation failed. - Sikertelen titkosítás-újraegyeztetés. + Nem sikerült a titkosítást újraegyeztetni. + No comment provided by engineer. + + + Encryption renegotiation in progress. + A titkosítás újraegyeztetése folyamatban van. No comment provided by engineer. Enter Passcode - Jelkód megadása + Adja meg a jelkódot No comment provided by engineer. Enter correct passphrase. - Helyes jelmondat bevitele. + Adja meg a helyes jelmondatot. No comment provided by engineer. Enter group name… - Csoportnév megadása… + Adja meg a csoport nevét… No comment provided by engineer. Enter passphrase - Jelmondat megadása + Adja meg a jelmondatot No comment provided by engineer. Enter passphrase… - Jelmondat megadása… + Adja meg a jelmondatot… No comment provided by engineer. Enter password above to show! - Jelszó megadása a megjelenítéshez! + Adja meg a jelszót fentebb a megjelenítéshez! No comment provided by engineer. Enter server manually - Kiszolgáló megadása kézzel + Adja meg a kiszolgálót kézzel No comment provided by engineer. Enter this device name… - Eszköznév megadása… + Adja meg ennek az eszköznek a nevét… No comment provided by engineer. Enter welcome message… - Üdvözlő üzenet megadása… + Adja meg az üdvözlőüzenetet… placeholder Enter welcome message… (optional) - Üdvözlő üzenet megadása… (opcionális) + Adja meg az üdvözlőüzenetet… (nem kötelező) placeholder @@ -2701,232 +3130,282 @@ Ez az egyszer használatos hivatkozása! Error aborting address change - Hiba a cím megváltoztatásának megszakításakor + Hiba történt a cím módosításának megszakításakor No comment provided by engineer. + + Error accepting conditions + Hiba történt a feltételek elfogadásakor + alert title + Error accepting contact request - Hiba történt a kapcsolatfelvételi kérelem elfogadásakor - No comment provided by engineer. - - - Error accessing database file - Hiba az adatbázisfájl elérésekor + Hiba történt a meghívási kérés elfogadásakor No comment provided by engineer. Error adding member(s) - Hiba a tag(-ok) hozzáadásakor + Hiba történt a tag(ok) hozzáadásakor No comment provided by engineer. + + Error adding server + Hiba történt a kiszolgáló hozzáadásakor + alert title + Error changing address - Hiba a cím megváltoztatásakor + Hiba történt a cím módosításakor + No comment provided by engineer. + + + Error changing connection profile + Hiba történt a kapcsolati profilra való váltáskor No comment provided by engineer. Error changing role - Hiba a szerepkör megváltoztatásakor + Hiba történt a szerepkör módosításakor No comment provided by engineer. Error changing setting - Hiba a beállítás megváltoztatásakor + Hiba történt a beállítás módosításakor + No comment provided by engineer. + + + Error changing to incognito! + Hiba történt az inkognitóprofilra való váltáskor! + No comment provided by engineer. + + + Error checking token status + Hiba történt a token állapotának ellenőrzésekor No comment provided by engineer. Error connecting to forwarding server %@. Please try later. - Hiba a(z) %@ továbbító kiszolgálóhoz való kapcsolódáskor. Próbálja meg később. + Hiba történt a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később. No comment provided by engineer. Error creating address - Hiba a cím létrehozásakor + Hiba történt a cím létrehozásakor No comment provided by engineer. Error creating group - Hiba a csoport létrehozásakor + Hiba történt a csoport létrehozásakor No comment provided by engineer. Error creating group link - Hiba a csoport hivatkozásának létrehozásakor + Hiba történt a csoporthivatkozás létrehozásakor No comment provided by engineer. + + Error creating list + Hiba történt a lista létrehozásakor + alert title + Error creating member contact - Hiba az ismerőssel történő kapcsolat létrehozásában + Hiba történt a partnerrel történő kapcsolat létrehozásában No comment provided by engineer. Error creating message - Hiba az üzenet létrehozásakor + Hiba történt az üzenet létrehozásakor No comment provided by engineer. Error creating profile! - Hiba a profil létrehozásakor! + Hiba történt a profil létrehozásakor! + No comment provided by engineer. + + + Error creating report + Hiba történt a jelentés létrehozásakor No comment provided by engineer. Error decrypting file - Hiba a fájl visszafejtésekor + Hiba történt a fájl visszafejtésekor No comment provided by engineer. Error deleting chat database - Hiba a csevegési adatbázis törlésekor + Hiba történt a csevegési adatbázis törlésekor No comment provided by engineer. Error deleting chat! - Hiba a csevegés törlésekor! + Hiba történt a csevegés törlésekor! No comment provided by engineer. Error deleting connection - Hiba a kapcsolat törlésekor + Hiba történt a kapcsolat törlésekor No comment provided by engineer. Error deleting database - Hiba az adatbázis törlésekor + Hiba történt az adatbázis törlésekor No comment provided by engineer. Error deleting old database - Hiba a régi adatbázis törlésekor + Hiba történt a régi adatbázis törlésekor No comment provided by engineer. Error deleting token - Hiba a token törlésekor + Hiba történt a token törlésekor No comment provided by engineer. Error deleting user profile - Hiba a felhasználói profil törlésekor + Hiba történt a felhasználó-profil törlésekor No comment provided by engineer. Error downloading the archive - Hiba az archívum letöltésekor + Hiba történt az archívum letöltésekor No comment provided by engineer. Error enabling delivery receipts! - Hiba a kézbesítési jelentések engedélyezésekor! + Hiba történt a kézbesítési jelentések engedélyezésekor! No comment provided by engineer. Error enabling notifications - Hiba az értesítések engedélyezésekor + Hiba történt az értesítések engedélyezésekor No comment provided by engineer. Error encrypting database - Hiba az adatbázis titkosításakor + Hiba történt az adatbázis titkosításakor No comment provided by engineer. Error exporting chat database - Hiba a csevegési adatbázis exportálásakor + Hiba történt a csevegési adatbázis exportálásakor No comment provided by engineer. Error exporting theme: %@ - Hiba a téma exportálásakor: %@ + Hiba történt a téma exportálásakor: %@ No comment provided by engineer. Error importing chat database - Hiba a csevegési adatbázis importálásakor + Hiba történt a csevegési adatbázis importálásakor No comment provided by engineer. Error joining group - Hiba a csoporthoz való csatlakozáskor + Hiba történt a csoporthoz való csatlakozáskor No comment provided by engineer. - - Error loading %@ servers - Hiba a %@ kiszolgálók betöltésekor + + Error loading servers + Hiba történt a kiszolgálók betöltésekor + alert title + + + Error migrating settings + Hiba történt a beállítások átköltöztetésekor No comment provided by engineer. Error opening chat - Hiba a csevegés megnyitásakor + Hiba történt a csevegés megnyitásakor No comment provided by engineer. Error receiving file - Hiba a fájl fogadásakor - No comment provided by engineer. + Hiba történt a fájl fogadásakor + alert title Error reconnecting server - Hiba a kiszolgálóhoz való újrakapcsolódáskor + Hiba történt a kiszolgálóhoz való újrakapcsolódáskor No comment provided by engineer. Error reconnecting servers - Hiba a kiszolgálókhoz való újrakapcsolódáskor + Hiba történt a kiszolgálókhoz való újrakapcsolódáskor No comment provided by engineer. + + Error registering for notifications + Hiba történt az értesítések regisztrálásakor + alert title + Error removing member - Hiba a tag eltávolításakor + Hiba történt a tag eltávolításakor No comment provided by engineer. + + Error reordering lists + Hiba történt a listák újrarendezésekor + alert title + Error resetting statistics - Hiba a statisztikák visszaállításakor - No comment provided by engineer. - - - Error saving %@ servers - Hiba történt a %@ kiszolgálók mentése közben + Hiba történt a statisztikák visszaállításakor No comment provided by engineer. Error saving ICE servers - Hiba az ICE kiszolgálók mentésekor + Hiba történt az ICE-kiszolgálók mentésekor No comment provided by engineer. + + Error saving chat list + Hiba történt a csevegési lista mentésekor + alert title + Error saving group profile - Hiba a csoportprofil mentésekor + Hiba történt a csoportprofil mentésekor No comment provided by engineer. Error saving passcode - Hiba a jelkód mentése közben + Hiba történt a jelkód mentésekor No comment provided by engineer. Error saving passphrase to keychain - Hiba a jelmondat kulcstartóba történő mentésekor + Hiba történt a jelmondat kulcstartóba történő mentésekor No comment provided by engineer. + + Error saving servers + Hiba történt a kiszolgálók mentésekor + alert title + Error saving settings - Hiba a beállítások mentésekor + Hiba történt a beállítások mentésekor when migrating Error saving user password - Hiba a felhasználó jelszavának mentésekor + Hiba történt a felhasználó jelszavának mentésekor No comment provided by engineer. Error scanning code: %@ - Hiba a kód beolvasása közben: %@ + Hiba történt a kód beolvasásakor: %@ No comment provided by engineer. Error sending email - Hiba az e-mail küldésekor + Hiba történt az e-mail elküldésekor No comment provided by engineer. @@ -2936,44 +3415,59 @@ Ez az egyszer használatos hivatkozása! Error sending message - Hiba az üzenet küldésekor + Hiba történt az üzenet elküldésekor No comment provided by engineer. Error setting delivery receipts! - Hiba történt a kézbesítési igazolások beállításakor! + Hiba történt a kézbesítési jelentések beállításakor! No comment provided by engineer. Error starting chat - Hiba a csevegés elindításakor + Hiba történt a csevegés elindításakor No comment provided by engineer. Error stopping chat - Hiba a csevegés megállításakor + Hiba történt a csevegés megállításakor + No comment provided by engineer. + + + Error switching profile + Hiba történt a profilváltáskor No comment provided by engineer. Error switching profile! - Hiba a profil váltásakor! - No comment provided by engineer. + Hiba történt a profilváltáskor! + alertTitle Error synchronizing connection - Hiba a kapcsolat szinkronizálása közben + Hiba történt a kapcsolat szinkronizálásakor + No comment provided by engineer. + + + Error testing server connection + Hiba történt a kiszolgáló kapcsolatának tesztelésekor No comment provided by engineer. Error updating group link - Hiba a csoport hivatkozás frissítésekor + Hiba történt a csoporthivatkozás frissítésekor No comment provided by engineer. Error updating message - Hiba az üzenet frissítésekor + Hiba történt az üzenet frissítésekor No comment provided by engineer. + + Error updating server + Hiba történt a kiszolgáló frissítésekor + alert title + Error updating settings Hiba történt a beállítások frissítésekor @@ -2981,17 +3475,17 @@ Ez az egyszer használatos hivatkozása! Error updating user privacy - Hiba a felhasználói beállítások frissítésekor + Hiba történt a felhasználói adatvédelem frissítésekor No comment provided by engineer. Error uploading the archive - Hiba az archívum feltöltésekor + Hiba történt az archívum feltöltésekor No comment provided by engineer. Error verifying passphrase: - Hiba a jelmondat ellenőrzésekor: + Hiba történt a jelmondat hitelesítésekor: No comment provided by engineer. @@ -3002,17 +3496,18 @@ Ez az egyszer használatos hivatkozása! Error: %@ Hiba: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid - Hiba: az URL érvénytelen + Hiba: a webcím érvénytelen No comment provided by engineer. Error: no database file - Hiba: nincs adatbázis fájl + Hiba: nincs adatbázisfájl No comment provided by engineer. @@ -3020,6 +3515,11 @@ Ez az egyszer használatos hivatkozása! Hibák No comment provided by engineer. + + Errors in servers configuration. + Hibák a kiszolgálók konfigurációjában. + servers error + Even when disabled in the conversation. Akkor is, ha le van tiltva a beszélgetésben. @@ -3035,6 +3535,11 @@ Ez az egyszer használatos hivatkozása! Kibontás chat item action + + Expired + Lejárt + token status text + Export database Adatbázis exportálása @@ -3062,7 +3567,7 @@ Ez az egyszer használatos hivatkozása! Exporting database archive… - Adatbázis archívum exportálása… + Adatbázis-archívum exportálása… No comment provided by engineer. @@ -3075,9 +3580,19 @@ Ez az egyszer használatos hivatkozása! Gyors és nem kell várni, amíg a feladó online lesz! No comment provided by engineer. + + Faster deletion of groups. + Gyorsabb csoporttörlés. + No comment provided by engineer. + Faster joining and more reliable messages. - Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés. + Gyorsabb csatlakozás és megbízhatóbb üzenetkézbesítés. + No comment provided by engineer. + + + Faster sending messages. + Gyorsabb üzenetküldés. No comment provided by engineer. @@ -3085,34 +3600,53 @@ Ez az egyszer használatos hivatkozása! Kedvenc swipe action + + Favorites + Kedvencek + No comment provided by engineer. + File error Fájlhiba - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Fájlhiba: +%@ + alert message + + + File is blocked by server operator: +%@. + A kiszolgáló üzemeltetője letiltotta a fájlt: +%@. + file error text File not found - most likely file was deleted or cancelled. - A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. + A fájl nem található – valószínűleg a fájlt törölték vagy visszavonták. file error text File server error: %@ - Fájlkiszolgáló hiba: %@ + Fájlkiszolgáló-hiba: %@ file error text File status - Fájlállapot + Fájl állapota No comment provided by engineer. File status: %@ - Fájlállapot: %@ + Fájl állapota: %@ copied message info File will be deleted from servers. - A fájl törölve lesz a kiszolgálóról. + A fájl törölve lesz a kiszolgálókról. No comment provided by engineer. @@ -3137,27 +3671,27 @@ Ez az egyszer használatos hivatkozása! Files & media - Fájlok és média + Fájlok és médiatartalmak No comment provided by engineer. Files and media - Fájlok és médiatartalom + Fájlok és médiatartalmak chat feature - - Files and media are prohibited in this group. - A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban. + + Files and media are prohibited. + A fájlok- és a médiatartalmak küldése le van tiltva. No comment provided by engineer. Files and media not allowed - Fájlok és média tartalom küldése le van tiltva + A fájlok- és médiatartalmak nincsenek engedélyezve No comment provided by engineer. Files and media prohibited! - A fájlok- és a médiatartalom küldése le van tiltva! + A fájlok- és a médiatartalmak küldése le van tiltva! No comment provided by engineer. @@ -3207,7 +3741,7 @@ Ez az egyszer használatos hivatkozása! Fix not supported by contact - Ismerős általi javítás nem támogatott + Partner általi javítás nem támogatott No comment provided by engineer. @@ -3215,21 +3749,71 @@ Ez az egyszer használatos hivatkozása! Csoporttag általi javítás nem támogatott No comment provided by engineer. + + For all moderators + Az összes moderátor számára + No comment provided by engineer. + + + For chat profile %@: + A(z) %@ nevű csevegési profilhoz: + servers error + For console Konzolhoz No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. + No comment provided by engineer. + + + For me + Csak magamnak + No comment provided by engineer. + + + For private routing + A privát útválasztáshoz + No comment provided by engineer. + + + For social media + A közösségi médiához + No comment provided by engineer. + Forward Továbbítás chat item action + + Forward %d message(s)? + Továbbít %d üzenetet? + alert title + Forward and save messages Üzenetek továbbítása és mentése No comment provided by engineer. + + Forward messages + Üzenetek továbbítása + alert action + + + Forward messages without files? + Továbbítja az üzeneteket fájlok nélkül? + alert message + + + Forward up to 20 messages at once. + Legfeljebb 20 üzenet egyszerre való továbbítása. + No comment provided by engineer. + Forwarded Továbbított @@ -3237,35 +3821,40 @@ Ez az egyszer használatos hivatkozása! Forwarded from - Továbbítva innen: + Továbbítva innen + No comment provided by engineer. + + + Forwarding %lld messages + %lld üzenet továbbítása No comment provided by engineer. Forwarding server %@ failed to connect to destination server %@. Please try later. - A(z) %@ továbbító kiszolgáló nem tudott csatlakozni a(z) %@ célkiszolgálóhoz. Próbálja meg később. + A(z) %@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %@ célkiszolgálóhoz. Próbálja meg később. No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. - A továbbító kiszolgáló címe nem kompatibilis a hálózati beállításokkal: %@. + A továbbítókiszolgáló címe nem kompatibilis a hálózati beállításokkal: %@. No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. - A továbbító kiszolgáló verziója nem kompatibilis a hálózati beállításokkal: %@. + A továbbítókiszolgáló verziója nem kompatibilis a hálózati beállításokkal: %@. No comment provided by engineer. Forwarding server: %1$@ Destination server error: %2$@ - Továbbító kiszolgáló: %1$@ -Célkiszolgáló hiba:%2$@ + Továbbítókiszolgáló: %1$@ +Célkiszolgáló-hiba: %2$@ snd error text Forwarding server: %1$@ Error: %2$@ - Továbbító kiszolgáló: %1$@ + Továbbítókiszolgáló: %1$@ Hiba: %2$@ snd error text @@ -3286,27 +3875,22 @@ Hiba: %2$@ Full name (optional) - Teljes név (opcionális) - No comment provided by engineer. - - - Full name: - Teljes név: + Teljes név (nem kötelező) No comment provided by engineer. Fully decentralized – visible only to members. - Teljesen decentralizált - kizárólag tagok számára látható. + Teljesen decentralizált – csak a tagok számára látható. No comment provided by engineer. Fully re-implemented - work in background! - Teljesen újra implementálva - háttérben történő működés! + Teljesen újra implementálva – háttérben történő működés! No comment provided by engineer. Further reduced battery usage - Tovább csökkentett akkumulátor használat + Tovább csökkentett akkumulátor-használat No comment provided by engineer. @@ -3314,6 +3898,11 @@ Hiba: %2$@ GIF-ek és matricák No comment provided by engineer. + + Get notified when mentioned. + Kapjon értesítést, ha megemlítik. + No comment provided by engineer. + Good afternoon! Jó napot! @@ -3346,72 +3935,37 @@ Hiba: %2$@ Group full name (optional) - Csoport teljes neve (opcionális) + A csoport teljes neve (nem kötelező) No comment provided by engineer. Group image - Csoportkép + Csoport profilképe No comment provided by engineer. Group invitation - Csoportos meghívó + Csoportmeghívó No comment provided by engineer. Group invitation expired - A csoport meghívó lejárt + A csoportmeghívó lejárt No comment provided by engineer. Group invitation is no longer valid, it was removed by sender. - A csoport meghívó már nem érvényes, a küldője törölte. + A csoportmeghívó már nem érvényes, a küldője eltávolította. No comment provided by engineer. Group link - Csoport hivatkozás + Csoporthivatkozás No comment provided by engineer. Group links - Csoport hivatkozások - No comment provided by engineer. - - - Group members can add message reactions. - Csoporttagok üzenetreakciókat adhatnak hozzá. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra) - No comment provided by engineer. - - - Group members can send SimpleX links. - A csoport tagjai küldhetnek SimpleX hivatkozásokat. - No comment provided by engineer. - - - Group members can send direct messages. - A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket. - No comment provided by engineer. - - - Group members can send disappearing messages. - A csoport tagjai küldhetnek eltűnő üzeneteket. - No comment provided by engineer. - - - Group members can send files and media. - A csoport tagjai küldhetnek fájlokat és médiatartalmakat. - No comment provided by engineer. - - - Group members can send voice messages. - A csoport tagjai küldhetnek hangüzeneteket. + Csoporthivatkozások No comment provided by engineer. @@ -3421,12 +3975,12 @@ Hiba: %2$@ Group moderation - Csoport moderáció + Csoport moderálása No comment provided by engineer. Group preferences - Csoport beállítások + Csoportbeállítások No comment provided by engineer. @@ -3441,27 +3995,37 @@ Hiba: %2$@ Group welcome message - Csoport üdvözlő üzenete + A csoport üdvözlőüzenete No comment provided by engineer. Group will be deleted for all members - this cannot be undone! - Csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! + A csoport törölve lesz az összes tag számára – ez a művelet nem vonható vissza! No comment provided by engineer. Group will be deleted for you - this cannot be undone! - A csoport törlésre kerül az ön számára - ez a művelet nem vonható vissza! + A csoport törölve lesz az Ön számára – ez a művelet nem vonható vissza! + No comment provided by engineer. + + + Groups + Csoportok No comment provided by engineer. Help - Segítség + Súgó + No comment provided by engineer. + + + Help admins moderating their groups. + Segítsen az adminisztrátoroknak a csoportjaik moderálásában. No comment provided by engineer. Hidden - Rejtett + Se név, se üzenet No comment provided by engineer. @@ -3471,12 +4035,12 @@ Hiba: %2$@ Hidden profile password - Rejtett profil jelszó + Rejtett profiljelszó No comment provided by engineer. Hide - Elrejt + Összecsukás chat item action @@ -3491,7 +4055,7 @@ Hiba: %2$@ Hide: - Elrejt: + Elrejtve: No comment provided by engineer. @@ -3501,7 +4065,7 @@ Hiba: %2$@ History is not sent to new members. - Az előzmények nem kerülnek elküldésre az új tagok számára. + Az előzmények nem lesznek elküldve az új tagok számára. No comment provided by engineer. @@ -3509,10 +4073,20 @@ Hiba: %2$@ Hogyan működik a SimpleX No comment provided by engineer. + + How it affects privacy + Hogyan érinti az adatvédelmet + No comment provided by engineer. + + + How it helps privacy + Hogyan segíti az adatvédelmet + No comment provided by engineer. + How it works Hogyan működik - No comment provided by engineer. + alert button How to @@ -3521,12 +4095,12 @@ Hiba: %2$@ How to use it - Hogyan használja + Használati útmutató No comment provided by engineer. How to use your servers - Kiszolgálók használata + Hogyan használja a saját kiszolgálóit No comment provided by engineer. @@ -3539,6 +4113,11 @@ Hiba: %2$@ ICE-kiszolgálók (soronként egy) No comment provided by engineer. + + IP address + IP-cím + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás közben, vagy ossza meg a hivatkozást. @@ -3546,22 +4125,22 @@ Hiba: %2$@ If you enter this passcode when opening the app, all app data will be irreversibly removed! - Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen törlődik! + Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen el lesz távolítva! No comment provided by engineer. If you enter your self-destruct passcode while opening the app: - Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: + Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő-jelkódot: 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). - Ha most kell használnia a csevegést, koppintson a ** Csináld később** elemre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis áttelepítése). + Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése). No comment provided by engineer. Ignore - Figyelmen kívül hagyás + Mellőzés No comment provided by engineer. @@ -3579,9 +4158,9 @@ Hiba: %2$@ Azonnal No comment provided by engineer. - - Immune to spam and abuse - Spam és visszaélések elleni védelem + + Immune to spam + Védett a kéretlen tartalommal szemben No comment provided by engineer. @@ -3591,7 +4170,7 @@ Hiba: %2$@ Import chat database? - Csevegési adatbázis importálása? + Importálja a csevegési adatbázist? No comment provided by engineer. @@ -3614,9 +4193,16 @@ Hiba: %2$@ Archívum importálása No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Továbbfejlesztett kézbesítés, csökkentett adatforgalom-használat. +További fejlesztések hamarosan! + No comment provided by engineer. + Improved message delivery - Továbbfejlesztett üzenetküldés + Továbbfejlesztett üzenetkézbesítés No comment provided by engineer. @@ -3636,7 +4222,7 @@ Hiba: %2$@ In reply to - Válasz neki + Válaszul erre No comment provided by engineer. @@ -3644,6 +4230,16 @@ Hiba: %2$@ Bejövő hívás csengőhangja No comment provided by engineer. + + Inappropriate content + Kifogásolt tartalom + report reason + + + Inappropriate profile + Kifogásolt profil + report reason + Incognito Inkognitó @@ -3651,17 +4247,17 @@ Hiba: %2$@ Incognito groups - Inkognitó csoportok + Inkognitócsoportok No comment provided by engineer. Incognito mode - Inkognitó mód + Inkognitómód No comment provided by engineer. Incognito mode protects your privacy by using a new random profile for each contact. - Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. + Az inkognitómód úgy védi a személyes adatait, hogy az összes partneréhez új, véletlenszerű profilt használ. No comment provided by engineer. @@ -3681,7 +4277,7 @@ Hiba: %2$@ Incompatible database version - Nem kompatibilis adatbázis verzió + Nem kompatibilis adatbázis-verzió No comment provided by engineer. @@ -3714,21 +4310,21 @@ Hiba: %2$@ A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Azonnali + No comment provided by engineer. + Instant push notifications will be hidden! - Az azonnali push értesítések elrejtésre kerülnek! + Az azonnali push-értesítések el lesznek rejtve! No comment provided by engineer. - - Instantly - Azonnal - No comment provided by engineer. - Interface - Felület + Kezelőfelület No comment provided by engineer. @@ -3736,6 +4332,31 @@ Hiba: %2$@ Kezelőfelület színei No comment provided by engineer. + + Invalid + Érvénytelen + token status text + + + Invalid (bad token) + Érvénytelen (hibás token) + token status text + + + Invalid (expired) + Érvénytelen (lejárt) + token status text + + + Invalid (unregistered) + Érvénytelen (nincs regisztrálva) + token status text + + + Invalid (wrong topic) + Érvénytelen (rossz topic) + token status text + Invalid QR code Érvénytelen QR-kód @@ -3743,12 +4364,12 @@ Hiba: %2$@ Invalid connection link - Érvénytelen kapcsolati hivatkozás + Érvénytelen kapcsolattartási hivatkozás No comment provided by engineer. Invalid display name! - Érvénytelen megjelenítendő felhaszálónév! + Érvénytelen megjelenítendő név! No comment provided by engineer. @@ -3774,7 +4395,7 @@ Hiba: %2$@ Invalid server address! Érvénytelen kiszolgálócím! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4417,11 @@ Hiba: %2$@ Tagok meghívása No comment provided by engineer. + + Invite to chat + Meghívás a csevegésbe + No comment provided by engineer. + Invite to group Meghívás a csoportba @@ -3811,19 +4437,19 @@ Hiba: %2$@ Az üzenetek végleges törlése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. - Az üzenetek végleges törlése le van tiltva ebben a csoportban. + + Irreversible message deletion is prohibited. + Az üzenetek végleges törlése le van tiltva. No comment provided by engineer. It allows having many anonymous connections without any shared data between them in a single chat profile. - Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. + Lehetővé teszi, hogy egyetlen csevegési profilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. No comment provided by engineer. It can happen when you or your connection used the old database backup. - Ez akkor fordulhat elő, ha ön vagy az ismerőse régi adatbázis biztonsági mentést használt. + Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt. No comment provided by engineer. @@ -3833,13 +4459,13 @@ Hiba: %2$@ 3. The connection was compromised. Ez akkor fordulhat elő, ha: 1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak. -2. Az üzenet visszafejtése sikertelen volt, mert vagy az ismerőse régebbi adatbázis biztonsági mentést használt. +2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt. 3. A kapcsolat sérült. No comment provided by engineer. It protects your IP address and connections. - Védi az IP-címét és a kapcsolatokat. + Védi az IP-címét és a kapcsolatait. No comment provided by engineer. @@ -3891,7 +4517,7 @@ Hiba: %2$@ Join your group? This is your link for group %@! Csatlakozik a csoportjához? -Ez az ön hivatkozása a(z) %@ csoporthoz! +Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No comment provided by engineer. @@ -3901,8 +4527,8 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Keep - Megtart - No comment provided by engineer. + Megtartás + alert action Keep conversation @@ -3916,8 +4542,8 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Keep unused invitation? - Fel nem használt meghívó megtartása? - No comment provided by engineer. + Megtartja a fel nem használt meghívót? + alert title Keep your connections @@ -3926,12 +4552,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! KeyChain error - Kulcstartó hiba + Kulcstartóhiba No comment provided by engineer. Keychain error - Kulcstartó hiba + Kulcstartóhiba No comment provided by engineer. @@ -3954,6 +4580,16 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Elhagyás swipe action + + Leave chat + Csevegés elhagyása + No comment provided by engineer. + + + Leave chat? + Elhagyja a csevegést? + No comment provided by engineer. + Leave group Csoport elhagyása @@ -3961,12 +4597,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Leave group? - Csoport elhagyása? + Elhagyja a csoportot? No comment provided by engineer. Let's talk in SimpleX Chat - Beszélgessünk a SimpleX Chat-ben + Beszélgessünk a SimpleX Chatben email subject @@ -3981,17 +4617,32 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Link mobile and desktop apps! 🔗 - Társítsa össze a mobil és az asztali alkalmazásokat! 🔗 + Társítsa össze a hordozható eszköz- és számítógépes alkalmazásokat! 🔗 No comment provided by engineer. Linked desktop options - Összekapcsolt számítógép beállítások + Társított számítógép beállítások No comment provided by engineer. Linked desktops - Összekapcsolt számítógépek + Társított számítógépek + No comment provided by engineer. + + + List + Lista + swipe action + + + List name and emoji should be different for all lists. + Az összes lista nevének és emodzsijának különbözőnek kell lennie. + No comment provided by engineer. + + + List name... + Lista neve… No comment provided by engineer. @@ -4004,11 +4655,6 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Élő üzenetek No comment provided by engineer. - - Local - Helyi - No comment provided by engineer. - Local name Helyi név @@ -4029,11 +4675,6 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Zárolási mód No comment provided by engineer. - - Make a private connection - Privát kapcsolat létrehozása - No comment provided by engineer. - Make one message disappear Egy üzenet eltüntetése @@ -4044,29 +4685,19 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Tegye priváttá a profilját! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. - Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. - No comment provided by engineer. - - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Sokan kérdezték: *ha a SimpleX-nek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?* + Győződjön meg arról, hogy a megadott WebRTC ICE-kiszolgálók címei megfelelő formátumúak, soronként elkülönítettek, és nincsenek duplikálva. No comment provided by engineer. Mark deleted for everyone - Jelölje meg mindenki számára töröltként + Jelölje meg az összes tag számára töröltként No comment provided by engineer. Mark read - Olvasottnak jelölés + Megjelölés olvasottként No comment provided by engineer. @@ -4086,7 +4717,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Media & file servers - Média és fájlkiszolgálók + Média- és fájlkiszolgálók No comment provided by engineer. @@ -4104,19 +4735,79 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Inaktív tag item status text + + Member reports + Tagok jelentései + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + A tag szerepköre a következőre fog módosulni: „%@”. A csevegés összes tagja értesítést fog kapni. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. - A tag szerepköre meg fog változni erre: „%@”. A csoport minden tagja értesítést kap róla. + A tag szerepköre a következőre fog módosulni: „%@”. A csoport az összes tagja értesítést fog kapni. No comment provided by engineer. Member role will be changed to "%@". The member will receive a new invitation. - A tag szerepköre meg fog változni erre: „%@”. A tag új meghívást fog kapni. + A tag szerepköre a következőre fog módosulni: „%@”. A tag új meghívást fog kapni. + No comment provided by engineer. + + + Member will be removed from chat - this cannot be undone! + A tag el lesz távolítva a csevegésből – ez a művelet nem vonható vissza! No comment provided by engineer. Member will be removed from group - this cannot be undone! - A tag eltávolítása a csoportból - ez a művelet nem vonható vissza! + A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza! + 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 irreversibly delete sent messages. (24 hours) + A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + No comment provided by engineer. + + + Members can report messsages to moderators. + A tagok jelenthetik az üzeneteket a moderátorok felé. + No comment provided by engineer. + + + Members can send SimpleX links. + A tagok küldhetnek SimpleX-hivatkozásokat. + No comment provided by engineer. + + + Members can send direct messages. + A tagok küldhetnek egymásnak közvetlen üzeneteket. + No comment provided by engineer. + + + Members can send disappearing messages. + A tagok küldhetnek eltűnő üzeneteket. + No comment provided by engineer. + + + Members can send files and media. + A tagok küldhetnek fájlokat és médiatartalmakat. + No comment provided by engineer. + + + Members can send voice messages. + A tagok küldhetnek hangüzeneteket. + No comment provided by engineer. + + + Mention members 👋 + Tagok említése 👋 No comment provided by engineer. @@ -4131,12 +4822,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message delivery receipts! - Üzenet kézbesítési jelentések! + Üzenetkézbesítési jelentések! No comment provided by engineer. Message delivery warning - Üzenet kézbesítési figyelmeztetés + Üzenetkézbesítési figyelmeztetés item status text @@ -4156,7 +4847,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message queue info - Üzenet-várakoztatási információ + Üzenetsorbaállítási információ No comment provided by engineer. @@ -4166,17 +4857,17 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message reactions are prohibited in this chat. - Az üzenetreakciók küldése le van tiltva ebben a csevegésben. + A reakciók hozzáadása az üzenetekhez le van tiltva ebben a csevegésben. No comment provided by engineer. - - Message reactions are prohibited in this group. - Az üzenetreakciók küldése le van tiltva ebben a csoportban. + + Message reactions are prohibited. + A reakciók hozzáadása az üzenetekhez le van tiltva. No comment provided by engineer. Message reception - Üzenet kézbesítési jelentés + Üzenetjelentés No comment provided by engineer. @@ -4184,6 +4875,11 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Üzenetkiszolgálók No comment provided by engineer. + + Message shape + Üzenetbuborék alakja + No comment provided by engineer. + Message source remains private. Az üzenet forrása titokban marad. @@ -4191,17 +4887,17 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message status - Üzenetállapot + Üzenet állapota No comment provided by engineer. Message status: %@ - Üzenetállapot: %@ + Üzenet állapota: %@ copied message info Message text - Üzenet szövege + Név és üzenet No comment provided by engineer. @@ -4221,9 +4917,14 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Messages from %@ will be shown! - A(z) %@ által írt üzenetek megjelennek! + %@ összes üzenete meg fog jelenni! No comment provided by engineer. + + Messages in this chat will never be deleted. + Az ebben a csevegésben lévő üzenetek soha nem lesznek törölve. + alert message + Messages received Fogadott üzenetek @@ -4234,14 +4935,19 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Elküldött üzenetek No comment provided by engineer. + + Messages were deleted after you selected them. + Az üzeneteket törölték miután kijelölte őket. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi. + Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve. 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. - Az üzeneteket, fájlokat és hívásokat **végpontok közötti kvantumrezisztens titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi. + Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve. No comment provided by engineer. @@ -4276,7 +4982,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Migrating database archive… - Adatbázis archívum migrálása… + Adatbázis-archívum átköltöztetése… No comment provided by engineer. @@ -4286,12 +4992,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Migration error: - Átköltöztetés hiba: + Átköltöztetési hiba: No comment provided by engineer. Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). - Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre az aktuális adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). + Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). No comment provided by engineer. @@ -4299,9 +5005,9 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Az átköltöztetés befejeződött No comment provided by engineer. - - Migrations: %@ - Átköltöztetések: %@ + + Migrations: + Átköltöztetések: No comment provided by engineer. @@ -4311,14 +5017,19 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Moderated at - Moderálva lett ekkor: + Moderálva No comment provided by engineer. Moderated at: %@ - Moderálva lett ekkor: %@ + Moderálva: %@ copied message info + + More + Továbbiak + swipe action + More improvements are coming soon! Hamarosan további fejlesztések érkeznek! @@ -4329,20 +5040,30 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Megbízhatóbb hálózati kapcsolat. No comment provided by engineer. + + More reliable notifications + Megbízhatóbb értesítések + No comment provided by engineer. + Most likely this connection is deleted. - Valószínűleg ez a kapcsolat törlésre került. + Valószínűleg ez a kapcsolat törölve lett. item status description Multiple chat profiles - Több csevegőprofil + Több csevegési profil No comment provided by engineer. Mute Némítás - swipe action + notification label action + + + Mute all + Összes némítása + notification label action Muted when inactive! @@ -4361,12 +5082,17 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Network connection - Internetkapcsolat + Hálózati kapcsolat + No comment provided by engineer. + + + Network decentralization + Hálózati decentralizáció No comment provided by engineer. 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. + Hálózati problémák – az üzenet többszöri elküldési kísérlet után lejárt. snd error text @@ -4374,6 +5100,11 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Hálózatkezelés No comment provided by engineer. + + Network operator + Hálózatüzemeltető + No comment provided by engineer. + Network settings Hálózati beállítások @@ -4384,14 +5115,29 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Hálózat állapota No comment provided by engineer. + + New + Új + token status text + New Passcode Új jelkód No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva. + No comment provided by engineer. + New chat - Új beszélgetés + Új csevegés No comment provided by engineer. @@ -4401,7 +5147,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! New contact request - Új kapcsolattartási kérelem + Új meghívási kérés notification @@ -4409,21 +5155,21 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Új kapcsolat: notification - - New database archive - Új adatbázis-archívum - No comment provided by engineer. - New desktop app! - Új asztali alkalmazás! + Új számítógép-alkalmazás! No comment provided by engineer. New display name - Új megjelenítési név + Új megjelenítendő név No comment provided by engineer. + + New events + Új események + notification + New in %@ Újdonságok a(z) %@ verzióban @@ -4449,6 +5195,11 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Új jelmondat… No comment provided by engineer. + + New server + Új kiszolgáló + No comment provided by engineer. + No Nem @@ -4459,14 +5210,29 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Nincs alkalmazás jelszó Authentication unavailable + + No chats + Nincsenek csevegések + No comment provided by engineer. + + + No chats found + Nem találhatók csevegések + No comment provided by engineer. + + + No chats in list %@ + Nincsenek csevegések a(z) %@ nevű listában + No comment provided by engineer. + No contacts selected - Nem kerültek ismerősök kiválasztásra + Nincs partner kijelölve No comment provided by engineer. No contacts to add - Nincs hozzáadandó ismerős + Nincs hozzáadandó partner No comment provided by engineer. @@ -4476,12 +5242,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! No device token! - Nincs eszköztoken! + Nincs készüléktoken! No comment provided by engineer. No direct connection yet, message is forwarded by admin. - Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja. + Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja. item status description @@ -4504,31 +5270,106 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Nincs információ, próbálja meg újratölteni No comment provided by engineer. + + No media & file servers. + Nincsenek média- és fájlkiszolgálók. + servers error + + + No message + Nincs üzenet + No comment provided by engineer. + + + No message servers. + Nincsenek üzenet-kiszolgálók. + servers error + No network connection Nincs hálózati kapcsolat No comment provided by engineer. + + No permission to record speech + Nincs jogosultság megadva a beszéd rögzítéséhez + No comment provided by engineer. + + + No permission to record video + Nincs jogosultság megadva a videó rögzítéséhez + No comment provided by engineer. + No permission to record voice message Nincs engedély a hangüzenet rögzítésére No comment provided by engineer. + + No push server + Helyi + No comment provided by engineer. + No received or sent files Nincsenek fogadott vagy küldött fájlok No comment provided by engineer. + + No servers for private message routing. + Nincsenek kiszolgálók a privát üzenet-útválasztáshoz. + servers error + + + No servers to receive files. + Nincsenek fájlfogadási kiszolgálók. + servers error + + + No servers to receive messages. + Nincsenek üzenetfogadási kiszolgálók. + servers error + + + No servers to send files. + Nincsenek fájlküldő-kiszolgálók. + servers error + + + No token! + Nincs token! + alert title + + + No unread chats + Nincsenek olvasatlan csevegések + No comment provided by engineer. + + + No user identifiers. + Nincsenek felhasználó-azonosítók. + No comment provided by engineer. + Not compatible! Nem kompatibilis! No comment provided by engineer. + + Notes + Jegyzetek + No comment provided by engineer. + Nothing selected - Semmi sincs kiválasztva + Nincs semmi kijelölve No comment provided by engineer. + + Nothing to forward! + Nincs mit továbbítani! + alert title + Notifications Értesítések @@ -4539,11 +5380,26 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Az értesítések le vannak tiltva! No comment provided by engineer. + + Notifications error + Értesítési hiba + alert title + + + Notifications privacy + Értesítési adatvédelem + No comment provided by engineer. + + + Notifications status + Értesítések állapota + alert title + Now admins can: - delete members' messages. - disable members ("observer" role) - Most már az adminok is: + Most már az adminisztrátorok is: - törölhetik a tagok üzeneteit. - letilthatnak tagokat („megfigyelő” szerepkör) No comment provided by engineer. @@ -4561,45 +5417,45 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Ok Rendben - No comment provided by engineer. + alert button Old database Régi adatbázis No comment provided by engineer. - - Old database archive - Régi adatbázis archívum - No comment provided by engineer. - One-time invitation link - Egyszer használatos meghívó hivatkozás + Egyszer használható meghívó No comment provided by engineer. Onion hosts will be **required** for connection. Requires compatible VPN. - A kapcsolódáshoz Onion kiszolgálókra lesz szükség. -VPN engedélyezése szükséges. + Onion-kiszolgálók **szükségesek** a kapcsolódáshoz. +Kompatibilis VPN szükséges. No comment provided by engineer. Onion hosts will be used when available. Requires compatible VPN. - Onion kiszolgálók használata, ha azok rendelkezésre állnak. + Onion-kiszolgálók használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges. No comment provided by engineer. Onion hosts will not be used. - Onion kiszolgálók nem lesznek használva. + Az onion-kiszolgálók nem lesznek használva. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Csak a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket. + + Only chat owners can change preferences. + Csak a csevegés tulajdonosai módosíthatják a csevegési beállításokat. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. + A felhasználói profilok, partnerek, csoportok és üzenetek csak az eszközön vannak tárolva a kliensen belül. No comment provided by engineer. @@ -4609,79 +5465,94 @@ VPN engedélyezése szükséges. Only group owners can change group preferences. - Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. + Csak a csoport tulajdonosai módosíthatják a csoportbeállításokat. No comment provided by engineer. Only group owners can enable files and media. - Csak a csoporttulajdonosok engedélyezhetik a fájlok- és a médiatartalmak küldését. + Csak a csoport tulajdonosai engedélyezhetik a fájlok- és a médiatartalmak küldését. No comment provided by engineer. Only group owners can enable voice messages. - Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. + Csak a csoport tulajdonosai engedélyezhetik a hangüzenetek küldését. + No comment provided by engineer. + + + Only sender and moderators see it + Csak a küldő és a moderátorok látják + No comment provided by engineer. + + + Only you and moderators see it + Csak Ön és a moderátorok látják No comment provided by engineer. Only you can add message reactions. - Csak ön adhat hozzá üzenetreakciókat. + Csak Ön adhat hozzá reakciókat az üzenetekhez. No comment provided by engineer. Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) - Véglegesen csak ön törölhet üzeneteket (ismerőse csak törlésre jelölheti meg őket ). (24 óra) + Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra) No comment provided by engineer. Only you can make calls. - Csak ön tud hívásokat indítani. + Csak Ön tud hívásokat indítani. No comment provided by engineer. Only you can send disappearing messages. - Csak ön tud eltűnő üzeneteket küldeni. + Csak Ön tud eltűnő üzeneteket küldeni. No comment provided by engineer. Only you can send voice messages. - Csak ön tud hangüzeneteket küldeni. + Csak Ön tud hangüzeneteket küldeni. No comment provided by engineer. Only your contact can add message reactions. - Csak az ismerős tud üzeneteakciókat adni. + Csak a partnere adhat hozzá reakciókat az üzenetekhez. No comment provided by engineer. Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) - Csak az ismerőse tudja az üzeneteket véglegesen törölni (ön csak törlésre jelölheti meg azokat). (24 óra) + Csak a partnere tudja az üzeneteket véglegesen törölni (Ön csak törlésre jelölheti meg azokat). (24 óra) No comment provided by engineer. Only your contact can make calls. - Csak az ismerős tud hívást indítani. + Csak a partnere tud hívást indítani. No comment provided by engineer. Only your contact can send disappearing messages. - Csak az ismerőse tud eltűnő üzeneteket küldeni. + Csak a partnere tud eltűnő üzeneteket küldeni. No comment provided by engineer. Only your contact can send voice messages. - Csak az ismerős tud hangüzeneteket küldeni. + Csak a partnere tud hangüzeneteket küldeni. No comment provided by engineer. Open Megnyitás - No comment provided by engineer. + alert action Open Settings Beállítások megnyitása No comment provided by engineer. + + Open changes + Módosítások megtekintése + No comment provided by engineer. + Open chat Csevegés megnyitása @@ -4689,39 +5560,48 @@ VPN engedélyezése szükséges. Open chat console - Csevegés konzol megnyitása + Csevegési konzol megnyitása authentication reason + + Open conditions + Feltételek megnyitása + No comment provided by engineer. + Open group Csoport megnyitása No comment provided by engineer. + + Open link? + alert title + Open migration to another device - Átköltöztetés megkezdése egy másik eszközre + Átköltöztetés indítása egy másik eszközre authentication reason - - Open server settings - Kiszolgáló beállításainak megnyitása - No comment provided by engineer. - - - Open user profiles - Felhasználói profilok megnyitása - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Nyílt forráskódú protokoll és forráskód – bárki üzemeltethet kiszolgálókat. - No comment provided by engineer. - Opening app… Az alkalmazás megnyitása… No comment provided by engineer. + + Operator + Üzemeltető + No comment provided by engineer. + + + Operator server + Kiszolgáló-üzemeltető + alert title + + + Or import archive file + Vagy archívumfájl importálása + No comment provided by engineer. + Or paste archive link Vagy az archívum hivatkozásának beillesztése @@ -4734,7 +5614,7 @@ VPN engedélyezése szükséges. Or securely share this file link - Vagy a fájl hivítkozásának biztonságos megosztása + Vagy ossza meg biztonságosan ezt a fájlhivatkozást No comment provided by engineer. @@ -4742,24 +5622,36 @@ VPN engedélyezése szükséges. 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. + + + Organize chats into lists + Csevegések listákba szervezése + No comment provided by engineer. + Other További No comment provided by engineer. - - Other %@ servers - További %@ kiszolgálók - No comment provided by engineer. + + Other file errors: +%@ + Egyéb fájlhiba: +%@ + alert message PING count - PING számláló + PING-ek száma No comment provided by engineer. PING interval - PING időköze + Időtartam a PING-ek között No comment provided by engineer. @@ -4769,7 +5661,7 @@ VPN engedélyezése szükséges. Passcode changed! - A jelkód megváltozott! + A jelkód módosult! No comment provided by engineer. @@ -4779,7 +5671,7 @@ VPN engedélyezése szükséges. Passcode not changed! - A jelkód nem változott! + A jelkód nem módosult! No comment provided by engineer. @@ -4787,14 +5679,19 @@ VPN engedélyezése szükséges. A jelkód beállítva! No comment provided by engineer. + + Password + Jelszó + No comment provided by engineer. + Password to show - Jelszó megjelenítése + Jelszó a megjelenítéshez No comment provided by engineer. Past member %@ - Már nem tag - %@ + (Már nem tag) %@ past/unknown group member @@ -4814,22 +5711,17 @@ VPN engedélyezése szükséges. Paste the link you received - Fogadott hivatkozás beillesztése + Kapott hivatkozás beillesztése No comment provided by engineer. Pending - Függő + Függőben No comment provided by engineer. - - People can connect to you only via the links you share. - Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak. - No comment provided by engineer. - - - Periodically - Rendszeresen + + Periodic + Időszakos No comment provided by engineer. @@ -4849,39 +5741,39 @@ VPN engedélyezése szükséges. Please ask your contact to enable calls. - Kérje meg az ismerősét, hogy engedélyezze a hívásokat. + Kérje meg a partnerét, hogy engedélyezze a hívásokat. No comment provided by engineer. Please ask your contact to enable sending voice messages. - Ismerős felkérése, hogy engedélyezze a hangüzenetek küldését. + Kérje meg a partnerét, hogy engedélyezze a hangüzenetek küldését. No comment provided by engineer. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. - Ellenőrizze, hogy a mobil és az asztali számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint az asztali számítógép tűzfalában engedélyezve van-e a kapcsolat. + 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. Minden további problémát osszon meg a fejlesztőkkel. No comment provided by engineer. Please check that you used the correct link or ask your contact to send you another one. - Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat. + Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg a partnerét, hogy küldjön egy másikat. No comment provided by engineer. Please check your network connection with %@ and try again. - Ellenőrizze hálózati kapcsolatát a(z) %@ segítségével, és próbálja újra. + Ellenőrizze a hálózati kapcsolatát a vele: %@, és próbálja újra. No comment provided by engineer. Please check yours and your contact preferences. - Ellenőrizze az ön és ismerőse beállításait. + Ellenőrizze a saját- és a partnere beállításait. No comment provided by engineer. Please confirm that network settings are correct for this device. - Ellenőrizze, hogy a hálózati beállítások megfelelőek-e ehhez az eszközhöz. + Ellenőrizze, hogy a hálózati beállítások megfelelők-e ehhez az eszközhöz. No comment provided by engineer. @@ -4893,22 +5785,22 @@ Hiba: %@ Please contact group admin. - Lépjen kapcsolatba a csoport adminnal. + Lépjen kapcsolatba a csoport adminisztrátorával. No comment provided by engineer. Please enter correct current passphrase. - Adja meg a helyes aktuális jelmondatát. + Adja meg a helyes, jelenlegi jelmondatot. No comment provided by engineer. Please enter the previous password after restoring database backup. This action can not be undone. - Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem vonható vissza. + Adja meg a korábbi jelszót az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem vonható vissza. No comment provided by engineer. Please remember or store it securely - there is no way to recover a lost passcode! - Jegyezze fel vagy tárolja el biztonságosan - az elveszett jelkódot nem lehet visszaállítani! + Jegyezze fel vagy tárolja el biztonságosan – az elveszett jelkódot nem lehet visszaállítani! No comment provided by engineer. @@ -4918,7 +5810,7 @@ Hiba: %@ Please restart the app and migrate the database to enable push notifications. - Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges push értesítések engedélyezéséhez. + Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges push-értesítések engedélyezéséhez. No comment provided by engineer. @@ -4928,14 +5820,34 @@ Hiba: %@ Please store passphrase securely, you will NOT be able to change it if you lose it. - Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. + Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja módosítani. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Próbálja meg letiltani és újra engedélyezni az értesítéseket. + token info + + + Please wait for token activation to complete. + Várjon, amíg a token aktiválása befejeződik. + token info + + + Please wait for token to be registered. + Várjon a token regisztrálására. + token info + Polish interface Lengyel kezelőfelület No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen @@ -4946,14 +5858,14 @@ Hiba: %@ Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. No comment provided by engineer. - - Preset server - Előre beállított kiszolgáló - No comment provided by engineer. - Preset server address - Előre beállított kiszolgáló címe + Az előre beállított kiszolgáló címe + No comment provided by engineer. + + + Preset servers + Előre beállított kiszolgálók No comment provided by engineer. @@ -4971,24 +5883,44 @@ Hiba: %@ Adatvédelem és biztonság No comment provided by engineer. + + Privacy for your customers. + Saját ügyfeleinek adatvédelme. + No comment provided by engineer. + + + Privacy policy and conditions of use. + Adatvédelmi szabályzat és felhasználási feltételek. + No comment provided by engineer. + Privacy redefined - Adatvédelem újraértelmezve + Újraértelmezett adatvédelem + 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 szerver üzemeltetői számára. No comment provided by engineer. Private filenames - Privát fájl nevek + Privát fájlnevek + No comment provided by engineer. + + + Private media file names. + Privát nevek a médiafájlokhoz. No comment provided by engineer. Private message routing - Privát üzenet útválasztás + Privát üzenet-útválasztás No comment provided by engineer. Private message routing 🚀 - Privát üzenet útválasztás 🚀 + Privát üzenet-útválasztás 🚀 No comment provided by engineer. @@ -5021,16 +5953,6 @@ Hiba: %@ Profilképek No comment provided by engineer. - - Profile name - Profilnév - No comment provided by engineer. - - - Profile name: - Profil neve: - No comment provided by engineer. - Profile password Profiljelszó @@ -5043,8 +5965,8 @@ Hiba: %@ Profile update will be sent to your contacts. - A profilfrissítés elküldésre került az ismerősök számára. - No comment provided by engineer. + A profilfrissítés el lesz küldve a partnerei számára. + alert message Prohibit audio/video calls. @@ -5053,22 +5975,27 @@ Hiba: %@ Prohibit irreversible message deletion. - Az üzenetek véglegesen való törlése le van tiltva. + Az elküldött üzenetek végleges törlése le van tiltva. No comment provided by engineer. Prohibit message reactions. - Az üzenetreakciók küldése le van tiltva. + A reakciók hozzáadása az üzenethez le van tiltva. No comment provided by engineer. Prohibit messages reactions. - Az üzenetreakciók tiltása. + A reakciók hozzáadása az üzenetekhez le van tiltva. + No comment provided by engineer. + + + Prohibit reporting messages to moderators. + Az üzenetek a moderátorok felé történő jelentésének megtiltása. No comment provided by engineer. Prohibit sending SimpleX links. - A SimpleX hivatkozások küldése le van tiltva. + A SimpleX-hivatkozások küldése le van tiltva. No comment provided by engineer. @@ -5083,7 +6010,7 @@ Hiba: %@ Prohibit sending files and media. - Fájlok- és a médiatartalom küldés letiltása. + A fájlok- és a médiatartalmak küldése le van tiltva. No comment provided by engineer. @@ -5093,7 +6020,7 @@ Hiba: %@ Protect IP address - IP-cím védelem + IP-cím védelme No comment provided by engineer. @@ -5104,23 +6031,23 @@ Hiba: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. - Védje IP-címét az ismerősei által kiválasztott üzenetküldő átjátszókkal szemben. -Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. + Védje az IP-címét a partnerei által kiválasztott üzenetváltási továbbítókiszolgálókkal szemben. +Engedélyezze a *Hálózat és kiszolgálók* menüben. No comment provided by engineer. Protect your chat profiles with a password! - Csevegési profiljok védelme jelszóval! + Védje meg a csevegési profiljait egy jelszóval! No comment provided by engineer. Protocol timeout - Protokoll időtúllépés + Protokoll időtúllépése No comment provided by engineer. Protocol timeout per KB - Protokoll időkorlát KB-onként + Protokoll időtúllépése kB-onként No comment provided by engineer. @@ -5133,19 +6060,24 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Proxyzott kiszolgálók No comment provided by engineer. + + Proxy requires password + A proxy jelszót igényel + No comment provided by engineer. + Push notifications - Push értesítések + Push-értesítések No comment provided by engineer. Push server - Push kiszolgáló + Push-kiszolgáló No comment provided by engineer. Quantum resistant encryption - Kvantumrezisztens titkosítás + Kvantumbiztos titkosítás No comment provided by engineer. @@ -5165,7 +6097,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Read - Olvasd el + Olvasott swipe action @@ -5173,26 +6105,21 @@ Engedélyezze a Beállítások / 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/app-settings.html#your-simplex-contact-address). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - További információ a GitHub tárolónkban. - 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óban](https://github.com/simplex-chat/simplex-chat#readme). @@ -5200,7 +6127,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Receipts are disabled - Üzenet kézbesítési jelentés letiltva + A kézbesítési jelentések le vannak tiltva No comment provided by engineer. @@ -5210,22 +6137,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Received at - Fogadva ekkor: + Fogadva No comment provided by engineer. Received at: %@ - Fogadva ekkor: %@ + Fogadva: %@ copied message info Received file event - Fogadott fájl esemény + Fogadott fájlesemény notification Received message - Fogadott üzenet + Fogadott üzenetbuborék színe message info title @@ -5235,27 +6162,27 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Received reply - Fogadott válasz + Fogadott válaszüzenet-buborék színe No comment provided by engineer. Received total - Összes fogadott + Összes fogadott üzenet No comment provided by engineer. Receiving address will be changed to a different server. Address change will complete after sender comes online. - A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. + A fogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása a feladó online állapotba kerülése után fejeződik be. No comment provided by engineer. Receiving file will be stopped. - A fájl fogadása leállt. + A fájl fogadása le fog állni. No comment provided by engineer. Receiving via - Fogadás a + Fogadás a következőn keresztül: No comment provided by engineer. @@ -5270,64 +6197,79 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Recipients see updates as you type them. - A címzettek a beírás közben látják a frissítéseket. + A címzettek a beírás közben látják a szövegváltozásokat. No comment provided by engineer. Reconnect - Újrakapcsolás + Újrakapcsolódás No comment provided by engineer. Reconnect all connected servers to force message delivery. It uses additional traffic. - Újrakapcsolódás az összes kiszolgálóhoz az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ. + Az összes kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. No comment provided by engineer. Reconnect all servers - Újrakapcsolódás minden kiszolgálóhoz + Újrakapcsolódás az összes kiszolgálóhoz No comment provided by engineer. Reconnect all servers? - Újrakapcsolódás minden kiszolgálóhoz? + Újrakapcsolódik az összes kiszolgálóhoz? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. - A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. + A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. No comment provided by engineer. Reconnect server? - Újrakapcsolódás a kiszolgálóhoz? + Újrakapcsolódik a kiszolgálóhoz? No comment provided by engineer. Reconnect servers? - Újrakapcsolódás a kiszolgálókhoz? + Újrakapcsolódik a kiszolgálókhoz? No comment provided by engineer. Record updated at - A bejegyzés frissítve + Bejegyzés frissítve No comment provided by engineer. Record updated at: %@ - A bejegyzés frissítve: %@ + Bejegyzés frissítve: %@ copied message info Reduced battery usage - Csökkentett akkumulátorhasználat + Csökkentett akkumulátor-használat No comment provided by engineer. + + Register + Regisztrálás + No comment provided by engineer. + + + Register notification token? + Regisztrálja az értesítési tokent? + token info + + + Registered + Regisztrálva + token status text + Reject Elutasítás reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5336,17 +6278,17 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reject contact request - Kapcsolatfelvételi kérelem elutasítása + Meghívási kérés elutasítása No comment provided by engineer. Relay server is only used if necessary. Another party can observe your IP address. - Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. + A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet. No comment provided by engineer. Relay server protects your IP address, but it can observe the duration of the call. - Az átjátszó kiszolgáló megvédi az IP-címet, de megfigyelheti a hívás időtartamát. + A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát. No comment provided by engineer. @@ -5354,6 +6296,11 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Eltávolítás No comment provided by engineer. + + Remove archive? + Eltávolítja az archívumot? + No comment provided by engineer. + Remove image Kép eltávolítása @@ -5366,17 +6313,17 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Remove member? - Biztosan eltávolítja? + Eltávolítja a tagot? No comment provided by engineer. Remove passphrase from keychain? - Jelmondat eltávolítása a kulcstartóból? + Eltávolítja a jelmondatot a kulcstartóból? No comment provided by engineer. Renegotiate - Újraegyzetetés + Újraegyeztetés No comment provided by engineer. @@ -5386,12 +6333,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Renegotiate encryption? - Titkosítás újraegyeztetése? + Újraegyezteti a titkosítást? No comment provided by engineer. Repeat connection request? - Kapcsolódási kérés megismétlése? + Megismétli a meghívási kérést? No comment provided by engineer. @@ -5406,7 +6353,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Repeat join request? - Csatlakozási kérés megismétlése? + Megismétli a meghívási kérést? No comment provided by engineer. @@ -5419,6 +6366,56 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Válasz chat item action + + Report + Jelentés + chat item action + + + Report content: only group moderators will see it. + Tartalom jelentése: csak a csoport moderátorai látják. + report reason + + + Report member profile: only group moderators will see it. + Tag profiljának jelentése: csak a csoport moderátorai látják. + report reason + + + Report other: only group moderators will see it. + Egyéb jelentés: csak a csoport moderátorai látják. + report reason + + + Report reason? + Jelentés indoklása? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Kéretlen tartalom jelentése: csak a csoport moderátorai látják. + report reason + + + Report violation: only group moderators will see it. + Szabálysértés jelentése: csak a csoport moderátorai látják. + report reason + + + Report: %@ + Jelentés: %@ + report in notification + + + Reporting messages to moderators is prohibited. + Az üzenetek jelentése a moderátorok felé le van tiltva. + No comment provided by engineer. + + + Reports + Jelentések + No comment provided by engineer. + Required Szükséges @@ -5426,7 +6423,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reset - Alaphelyzetbe állítás + Visszaállítás No comment provided by engineer. @@ -5436,17 +6433,17 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reset all statistics - Minden statisztika visszaállítása + Az összes statisztika visszaállítása No comment provided by engineer. Reset all statistics? - Minden statisztika visszaállítása? + Visszaállítja az összes statisztikát? No comment provided by engineer. Reset colors - Színek alaphelyzetbe állítása + Színek visszaállítása No comment provided by engineer. @@ -5456,7 +6453,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Reset to defaults - Alaphelyzetbe állítás + Visszaállítás alapértelmezettre No comment provided by engineer. @@ -5486,12 +6483,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Restore database backup? - Adatbázismentés visszaállítása? + Visszaállítja az adatbázismentést? No comment provided by engineer. Restore database error - Hiba az adatbázis visszaállításakor + Hiba történt az adatbázis visszaállításakor No comment provided by engineer. @@ -5504,6 +6501,11 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Felfedés chat item action + + Review conditions + Feltételek felülvizsgálata + No comment provided by engineer. + Revoke Visszavonás @@ -5516,7 +6518,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Revoke file? - Fájl visszavonása? + Visszavonja a fájlt? No comment provided by engineer. @@ -5534,6 +6536,11 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SMP-kiszolgáló No comment provided by engineer. + + SOCKS proxy + SOCKS-proxy + No comment provided by engineer. + Safely receive files Fájlok biztonságos fogadása @@ -5547,17 +6554,18 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save Mentés - chat item action + alert button +chat item action Save (and notify contacts) - Mentés (és az ismerősök értesítése) - No comment provided by engineer. + Mentés (és a partnerek értesítése) + alert button Save and notify contact - Mentés és ismerős értesítése - No comment provided by engineer. + Mentés és a partner értesítése + alert button Save and notify group members @@ -5571,27 +6579,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save and update group profile - Mentés és csoportprofil frissítése - No comment provided by engineer. - - - Save archive - Archívum mentése - No comment provided by engineer. - - - Save auto-accept settings - Automatikus elfogadási beállítások mentése + Mentés és a csoportprofil frissítése No comment provided by engineer. Save group profile - Csoportprofil elmentése + Csoportprofil mentése + No comment provided by engineer. + + + Save list + Lista mentése No comment provided by engineer. Save passphrase and open chat - Jelmondat elmentése és csevegés megnyitása + Jelmondat mentése és a csevegés megnyitása No comment provided by engineer. @@ -5601,12 +6604,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save preferences? - Beállítások mentése? - No comment provided by engineer. + Menti a beállításokat? + alert title Save profile password - Felhasználói fiók jelszavának mentése + Profiljelszó mentése No comment provided by engineer. @@ -5616,19 +6619,19 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Save servers? - Kiszolgálók mentése? - No comment provided by engineer. - - - Save settings? - Beállítások mentése? - No comment provided by engineer. + Menti a kiszolgálókat? + alert title Save welcome message? - Üdvözlőszöveg mentése? + Menti az üdvözlőüzenetet? No comment provided by engineer. + + Save your profile? + Menti a profilt? + alert title + Saved Mentett @@ -5636,12 +6639,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Saved WebRTC ICE servers will be removed - A mentett WebRTC ICE kiszolgálók eltávolításra kerülnek + A mentett WebRTC ICE-kiszolgálók el lesznek távolítva No comment provided by engineer. Saved from - Mentve innen: + Elmentve innen No comment provided by engineer. @@ -5649,6 +6652,11 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Mentett üzenet message info title + + Saving %lld messages + %lld üzenet mentése + No comment provided by engineer. + Scale Méretezés @@ -5666,7 +6674,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Scan QR code from desktop - QR-kód beolvasása számítógépről + QR-kód beolvasása a számítógépről No comment provided by engineer. @@ -5676,7 +6684,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Scan security code from your contact's app. - Biztonsági kód beolvasása ismerős általi alkalmazásból. + Biztonsági kód beolvasása a partnere alkalmazásából. No comment provided by engineer. @@ -5691,22 +6699,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Search bar accepts invitation links. - A keresősáv elfogadja a meghívó hivatkozásokat. + A keresősáv elfogadja a meghívási hivatkozásokat. No comment provided by engineer. Search or paste SimpleX link - Keresés, vagy SimpleX hivatkozás beillesztése + Keresés vagy SimpleX-hivatkozás beillesztése No comment provided by engineer. Secondary - Másodlagos + Másodlagos szín No comment provided by engineer. Secure queue - Biztonságos várólista + Biztonságos sorba állítás server test step @@ -5726,17 +6734,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Select - Választás + Kijelölés chat item action + + Select chat profile + Csevegési profil kijelölése + No comment provided by engineer. + Selected %lld - %lld kiválasztva + %lld kijelölve No comment provided by engineer. Selected chat preferences prohibit this message. - A kiválasztott csevegési beállítások tiltják ezt az üzenetet. + A kijelölt csevegési beállítások tiltják ezt az üzenetet. No comment provided by engineer. @@ -5746,17 +6759,17 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Self-destruct passcode - Önmegsemmisítési jelkód + Önmegsemmisítő-jelkód No comment provided by engineer. Self-destruct passcode changed! - Az önmegsemmisítési jelkód megváltozott! + Az önmegsemmisítő-jelkód módosult! No comment provided by engineer. Self-destruct passcode enabled! - Az önmegsemmisítési jelkód engedélyezve! + Az önmegsemmisítő-jelkód engedélyezve! No comment provided by engineer. @@ -5766,7 +6779,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Send a live message - it will update for the recipient(s) as you type it - Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja + Élő üzenet küldése – az üzenet a címzett(ek) számára valós időben frissül, ahogy Ön beírja az üzenetet No comment provided by engineer. @@ -5791,7 +6804,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Send link previews - Hivatkozás előnézetek küldése + Hivatkozás előnézete No comment provided by engineer. @@ -5806,12 +6819,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Send messages directly when IP address is protected and your or destination server does not support private routing. - Közvetlen üzenetküldés, ha az IP-cím védett és az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha az IP-cím védett és a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. No comment provided by engineer. Send messages directly when your or destination server does not support private routing. - Közvetlen üzenetküldés, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. No comment provided by engineer. @@ -5819,89 +6832,89 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Értesítések küldése No comment provided by engineer. - - Send notifications: - Értesítések küldése: + + Send private reports + Privát jelentések küldése No comment provided by engineer. Send questions and ideas - Ötletek és kérdések beküldése + Ötletek és javaslatok No comment provided by engineer. Send receipts - Üzenet kézbesítési jelentések + Kézbesítési jelentések küldése No comment provided by engineer. Send them from gallery or custom keyboards. - Küldje el őket galériából vagy egyedi billentyűzetekről. + 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. - Az utolsó 100 üzenet elküldése az új tagoknak. + Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára. No comment provided by engineer. Sender cancelled file transfer. A fájl küldője visszavonta az átvitelt. - No comment provided by engineer. + alert message Sender may have deleted the connection request. - A küldő törölhette a kapcsolódási kérelmet. + A küldője törölhette a meghívási kérést. 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élyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára. + 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. No comment provided by engineer. Sending delivery receipts will be enabled for all contacts. - A kézbesítési jelentés küldése minden ismerős számára engedélyezésre kerül. + A kézbesítési jelentések küldése az összes partnere számára engedélyezve lesz. No comment provided by engineer. Sending file will be stopped. - A fájl küldése leállt. + A fájl küldése le fog állni. No comment provided by engineer. Sending receipts is disabled for %lld contacts - A kézbesítési jelentések küldése le van tiltva %lld ismerősnél + A kézbesítési jelentések le vannak tiltva %lld partnernél No comment provided by engineer. Sending receipts is disabled for %lld groups - A kézbesítési jelentések küldése le van tiltva %lld csoportban + A kézbesítési jelentések le vannak tiltva %lld csoportban No comment provided by engineer. Sending receipts is enabled for %lld contacts - A kézbesítési jelentések küldése engedélyezve van %lld ismerős számára + A kézbesítési jelentések engedélyezve vannak %lld partnernél No comment provided by engineer. Sending receipts is enabled for %lld groups - A kézbesítési jelentések küldése engedélyezve van %lld csoportban + A kézbesítési jelentések engedélyezve vannak %lld csoportban No comment provided by engineer. Sending via - Küldés ezen keresztül + Küldés a következőn keresztül: No comment provided by engineer. Sent at - Elküldve ekkor: + Elküldve No comment provided by engineer. Sent at: %@ - Elküldve ekkor: %@ + Elküldve: %@ copied message info @@ -5911,12 +6924,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Sent file event - Elküldött fájl esemény + Elküldött fájlesemény notification Sent message - Elküldött üzenet + Üzenetbuborék színe message info title @@ -5926,24 +6939,34 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Sent messages will be deleted after set time. - Az elküldött üzenetek törlésre kerülnek a beállított idő után. + Az elküldött üzenetek törölve lesznek a beállított idő után. No comment provided by engineer. Sent reply - Elküldött válasz + Válaszüzenet-buborék színe No comment provided by engineer. Sent total - Összes elküldött + Összes elküldött üzenet No comment provided by engineer. Sent via proxy - Proxyn keresztül küldve + Proxyn keresztül küldött No comment provided by engineer. + + Server + Kiszolgáló + No comment provided by engineer. + + + Server added to operator %@. + Kiszolgáló hozzáadva a következő üzemeltetőhöz: %@. + alert message + Server address Kiszolgáló címe @@ -5959,14 +6982,29 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. A kiszolgáló címe nem kompatibilis a hálózati beállításokkal: %@. No comment provided by engineer. + + Server operator changed. + A kiszolgáló üzemeltetője módosult. + alert title + + + Server operators + Kiszolgálóüzemeltetők + No comment provided by engineer. + + + Server protocol changed. + A kiszolgáló-protokoll módosult. + alert title + 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 jelszavát + A kiszolgálónak engedélyre van szüksége a sorba állítás létrehozásához, ellenőrizze a jelszavát server test error Server requires authorization to upload, check password - A kiszolgálónak engedélyre van szüksége a várólisták feltöltéséhez, ellenőrizze jelszavát + A kiszolgálónak hitelesítésre van szüksége a feltöltéshez, ellenőrizze jelszavát server test error @@ -5996,12 +7034,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Servers info - információk a kiszolgálókról + Információk a kiszolgálókról No comment provided by engineer. Servers statistics will be reset - this cannot be undone! - A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza! + A kiszolgálók statisztikái visszaállnak – ez a művelet nem vonható vissza! No comment provided by engineer. @@ -6014,9 +7052,14 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Beállítva 1 nap No comment provided by engineer. + + Set chat name… + Csevegés nevének beállítása… + No comment provided by engineer. + Set contact name… - Ismerős nevének beállítása… + Partner nevének beállítása… No comment provided by engineer. @@ -6031,7 +7074,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Set it instead of system authentication. - Rendszerhitelesítés helyetti beállítás. + Beállítás a rendszer-hitelesítés helyett. + No comment provided by engineer. + + + Set message expiration in chats. + Üzenetek eltűnési idejének módosítása a csevegésekben. No comment provided by engineer. @@ -6051,7 +7099,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Set the message shown to new members! - Megjelenő üzenet beállítása az új tagok számára! + Megjelenítendő üzenet beállítása az új tagok számára! No comment provided by engineer. @@ -6064,19 +7112,35 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Beállítások No comment provided by engineer. + + Settings were changed. + A beállítások módosultak. + alert message + Shape profile images - Profilkép alakzat + Profilkép alakzata No comment provided by engineer. Share Megosztás - chat item action + alert action +chat item action Share 1-time link - Egyszer használatos hivatkozás megosztása + Egyszer használható meghívó megosztása + No comment provided by engineer. + + + Share 1-time link with a friend + Egyszer használható meghívó megosztása egy baráttal + No comment provided by engineer. + + + Share SimpleX address on social media. + SimpleX-cím megosztása a közösségi médiában. No comment provided by engineer. @@ -6084,10 +7148,15 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Cím megosztása No comment provided by engineer. + + Share address publicly + Cím nyilvános megosztása + No comment provided by engineer. + Share address with contacts? - Megosztja a címet az ismerőseivel? - No comment provided by engineer. + Megosztja a címet a partnereivel? + alert title Share from other apps. @@ -6096,22 +7165,32 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Share link - Hivatkozás megosztása + Megosztás + No comment provided by engineer. + + + Share profile + Profil megosztása No comment provided by engineer. Share this 1-time invite link - Egyszer használatos meghívó hivatkozás megosztása + Ennek az egyszer használható meghívónak a megosztása No comment provided by engineer. Share to SimpleX - Megosztás a SimpleX-ben + Megosztás a SimpleXben No comment provided by engineer. Share with contacts - Megosztás az ismerősökkel + Megosztás a partnerekkel + No comment provided by engineer. + + + Short link + Rövid hivatkozás No comment provided by engineer. @@ -6131,12 +7210,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Show last messages - Utolsó üzenetek megjelenítése + Legutóbbi üzenet előnézetének megjelenítése No comment provided by engineer. Show message status - Üzenet állapot megjelenítése + Üzenet állapotának megjelenítése No comment provided by engineer. @@ -6146,7 +7225,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Show preview - Előnézet megjelenítése + Értesítés előnézete No comment provided by engineer. @@ -6156,7 +7235,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Show: - Megjelenítés: + Megjelenítve: No comment provided by engineer. @@ -6166,7 +7245,12 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX Address - SimpleX cím + SimpleX-cím + No comment provided by engineer. + + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba. No comment provided by engineer. @@ -6176,7 +7260,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX Lock - SimpleX zár + SimpleX-zár No comment provided by engineer. @@ -6186,19 +7270,34 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX Lock not enabled! - A SimpleX zár nincs bekapcsolva! + A SimpleX-zár nincs bekapcsolva! No comment provided by engineer. SimpleX Lock turned on - SimpleX zár bekapcsolva + SimpleX-zár bekapcsolva No comment provided by engineer. SimpleX address - SimpleX cím + 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. + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX-cím vagy egyszer használható meghívó? + No comment provided by engineer. + + + SimpleX channel link + SimpleX-csatornahivatkozás + simplex link type + SimpleX contact address SimpleX kapcsolattartási cím @@ -6211,32 +7310,37 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. SimpleX group link - SimpleX csoport hivatkozás + SimpleX-csoporthivatkozás simplex link type SimpleX links - SimpleX hivatkozások + SimpleX-hivatkozások chat feature - - SimpleX links are prohibited in this group. - A SimpleX hivatkozások küldése le van tiltva ebben a csoportban. + + SimpleX links are prohibited. + A SimpleX-hivatkozások küldése le van tiltva. No comment provided by engineer. SimpleX links not allowed - A SimpleX hivatkozások küldése le van tiltva + A SimpleX-hivatkozások küldése le van tiltva No comment provided by engineer. SimpleX one-time invitation - SimpleX egyszer használatos meghívó + Egyszer használható SimpleX-meghívó simplex link type + + SimpleX protocols reviewed by Trail of Bits. + A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva. + No comment provided by engineer. + Simplified incognito mode - Egyszerűsített inkognító mód + Egyszerűsített inkognitómód No comment provided by engineer. @@ -6264,26 +7368,44 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Enyhe blur media + + Some app settings were not migrated. + Egyes alkalmazásbeállítások nem lettek átköltöztetve. + No comment provided by engineer. + Some file(s) were not exported: - Néhány fájl nem került exportálásra: + Néhány fájl nem lett exportálva: No comment provided by engineer. Some non-fatal errors occurred during import - you may see Chat console for more details. - Néhány nem végzetes hiba történt az importálás közben – további részletekért a csevegési konzolban olvashat. + Néhány nem végzetes hiba történt az importáláskor – további részleteket a csevegési konzolban olvashat. No comment provided by engineer. Some non-fatal errors occurred during import: - Néhány nem végzetes hiba történt az importálás közben: + Néhány nem végzetes hiba történt az importáláskor: No comment provided by engineer. + + Some servers failed the test: +%@ + Néhány kiszolgáló megbukott a teszten: +%@ + alert message + Somebody Valaki notification title + + Spam + Kéretlen tartalom + blocking reason +report reason + Square, circle, or anything in between. Négyzet, kör vagy bármi a kettő között. @@ -6296,7 +7418,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Start chat? - Csevegés indítása? + Elindítja a csevegést? No comment provided by engineer. @@ -6306,7 +7428,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Starting from %@. - Kezdve ettől %@. + Statisztikagyűjtés kezdete: %@. No comment provided by engineer. @@ -6329,19 +7451,14 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Csevegési szolgáltatás megállítása No comment provided by engineer. - - Stop chat to enable database actions - Csevegés megállítása az adatbázis-műveletek engedélyezéséhez - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. - A csevegés megállítása a csevegő adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni. + 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. No comment provided by engineer. Stop chat? - Csevegési szolgáltatás megállítása? + Megállítja a csevegést? No comment provided by engineer. @@ -6351,29 +7468,34 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Stop receiving file? - Fájl fogadás megállítása? + Megállítja a fájlfogadást? No comment provided by engineer. Stop sending file? - Fájl küldés megállítása? + Megállítja a fájlküldést? No comment provided by engineer. Stop sharing Megosztás megállítása - No comment provided by engineer. + alert action Stop sharing address? - Címmegosztás megállítása? - No comment provided by engineer. + Megállítja a címmegosztást? + alert title Stopping chat Csevegés megállítása folyamatban No comment provided by engineer. + + Storage + Tárhely + No comment provided by engineer. + Strong Erős @@ -6396,12 +7518,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Subscriptions ignored - Elutasított feliratkozások + Mellőzött feliratkozások No comment provided by engineer. Support SimpleX Chat - Támogassa a SimpleX Chatet + SimpleX Chat támogatása + No comment provided by engineer. + + + Switch audio and video during the call. + Hang/Videó váltása hívás közben. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Csevegési profilváltás az egyszer használható meghívókhoz. No comment provided by engineer. @@ -6411,17 +7543,22 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. System authentication - Rendszerhitelesítés + Rendszer-hitelesítés No comment provided by engineer. TCP connection - TCP kapcsolat + TCP-kapcsolat No comment provided by engineer. TCP connection timeout - TCP kapcsolat időtúllépés + TCP-kapcsolat időtúllépése + No comment provided by engineer. + + + TCP port for messaging + TCP-port az üzenetváltáshoz No comment provided by engineer. @@ -6439,11 +7576,21 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Farok + No comment provided by engineer. + Take picture Kép készítése 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 button Koppintson a @@ -6451,7 +7598,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Tap to Connect - Koppintson a kapcsolódáshoz + Koppintson ide a kapcsolódáshoz No comment provided by engineer. @@ -6461,34 +7608,39 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Tap to join - Koppintson a csatlakozáshoz + Koppintson ide a csatlakozáshoz No comment provided by engineer. Tap to join incognito - Koppintson az inkognitóban való csatlakozáshoz + Koppintson ide az inkognitóban való kapcsolódáshoz No comment provided by engineer. Tap to paste link - Koppintson a hivatkozás beillesztéséhez + Koppintson ide a hivatkozás beillesztéséhez No comment provided by engineer. Tap to scan - Koppintson a beolvasáshoz + Koppintson ide a QR-kód beolvasásához No comment provided by engineer. Temporary file error Ideiglenes fájlhiba - No comment provided by engineer. + file error alert title Test failed at step %@. - A teszt sikertelen volt a(z) %@ lépésnél. + A teszt a(z) %@ lépésnél sikertelen volt. server test failure + + Test notifications + Értesítések tesztelése + No comment provided by engineer. + Test server Kiszolgáló tesztelése @@ -6502,7 +7654,7 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Tests failed! Sikertelen tesztek! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6511,29 +7663,29 @@ Engedélyezze a Beállítások / Hálózat és kiszolgálók menüben. Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! - Köszönet a felhasználóknak – [hozzájárulás a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + Köszönet a felhasználóknak [a Weblate-en való közreműködésért](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. Thanks to the users – contribute via Weblate! - Köszönet a felhasználóknak - hozzájárulás a Weblaten! - No comment provided by engineer. - - - The 1st platform without any user identifiers – private by design. - Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. + Köszönet a felhasználóknak a Weblate-en való közreműködésért! No comment provided by engineer. The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. - A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). -Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. + A következő üzenet azonosítója érvénytelen (kisebb vagy egyenlő az előzővel). +Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. No comment provided by engineer. The app can notify you when you receive messages or contact requests - please open settings to enable. - Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatfelvételi kéréseket kap – beállítások megnyitása az engedélyezéshez. + Az alkalmazás értesíteni fogja, amikor üzeneteket vagy meghívási kéréseket kap – ezt a beállítások menüben engedélyezheti. + 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. @@ -6543,27 +7695,32 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The attempt to change database passphrase was not completed. - Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be. + Az adatbázis jelmondatának módosítására tett kísérlet nem fejeződött be. No comment provided by engineer. The code you scanned is not a SimpleX link QR code. - A beolvasott kód nem egy SimpleX hivatkozás QR-kód. + A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás. + 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. No comment provided by engineer. The connection you accepted will be cancelled! - Az ön által elfogadott kapcsolat vissza lesz vonva! + Az Ön által elfogadott kérelem vissza lesz vonva! No comment provided by engineer. The contact you shared this link with will NOT be able to connect! - Ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni! + A partnere, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni! No comment provided by engineer. The created archive is available via app Settings / Database / Old database archive. - A létrehozott archívum a Beállítások / Adatbázis / Régi adatbázis-archívum menüben érhető el. + A létrehozott archívum a „Beállítások / Adatbázis / Régi adatbázis-archívum” menüben érhető el. No comment provided by engineer. @@ -6571,44 +7728,49 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. - Az előző üzenet ellenőrzőösszege különbözik. + Az előző üzenet hasítóértéke különbözik. No comment provided by engineer. The message will be deleted for all members. - Az üzenet minden tag számára törlésre kerül. + Az üzenet az összes tag számára törölve lesz. No comment provided by engineer. The message will be marked as moderated for all members. - Az üzenet minden tag számára moderáltként lesz megjelölve. + Az üzenet az összes tag számára moderáltként lesz megjelölve. No comment provided by engineer. The messages will be deleted for all members. - Az üzenetek minden tag számára törlésre kerülnek. + Az üzenetek az összes tag számára törölve lesznek. No comment provided by engineer. The messages will be marked as moderated for all members. - Az üzenetek moderáltként lesznek megjelölve minden tag számára. - No comment provided by engineer. - - - The next generation of private messaging - A privát üzenetküldés következő generációja + Az üzenetek az összes tag számára moderáltként lesznek megjelölve. No comment provided by engineer. The old database was not removed during the migration, it can be deleted. - A régi adatbázis nem került eltávolításra az átköltöztetés közben, így törölhető. + 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 profile is only shared with your contacts. - Profilja csak az ismerőseivel kerül megosztásra. + + The same conditions will apply to operator **%@**. + Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + A második előre beállított üzemeltető az alkalmazásban! No comment provided by engineer. @@ -6623,12 +7785,22 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The servers for new connections of your current chat profile **%@**. - Jelenlegi profil új ismerőseinek kiszolgálói **%@**. + A jelenlegi **%@** nevű csevegési profiljához tartozó új kapcsolatok kiszolgálói. + No comment provided by engineer. + + + The servers for new files of your current chat profile **%@**. + A jelenlegi **%@** nevű csevegési profiljához tartozó új fájlok kiszolgálói. No comment provided by engineer. The text you pasted is not a SimpleX link. - A beillesztett szöveg nem egy SimpleX hivatkozás. + A beillesztett szöveg nem egy SimpleX-hivatkozás. + No comment provided by engineer. + + + The uploaded database archive will be permanently removed from the servers. + A feltöltött adatbázis-archívum véglegesen el lesz távolítva a kiszolgálókról. No comment provided by engineer. @@ -6636,29 +7808,39 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Témák No comment provided by engineer. + + These conditions will also apply for: **%@**. + Ezek a feltételek lesznek elfogadva a következő számára is: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. - Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak. + Ezek a beállítások csak a jelenlegi **%@** nevű csevegési profiljára vonatkoznak. No comment provided by engineer. They can be overridden in contact and group settings. - Ezek felülbírálhatóak az ismerős- és csoportbeállításokban. + Ezek felülbírálhatók a partner- és csoportbeállításokban. 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. - Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. + Ez a művelet nem vonható vissza – az összes fogadott és küldött fájl a médiatartalmakkal együtt törölve lesznek. Az alacsony felbontású képek viszont megmaradnak. 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. - Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet. + Ez a művelet nem vonható vissza – a kijelöltnél korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Ez a művelet nem vonható vissza - profiljai, ismerősei, üzenetei és fájljai visszafordíthatatlanul törlésre kerülnek. + Ez a művelet nem vonható vissza – profiljai, partnerei, üzenetei és fájljai véglegesen törölve lesznek. No comment provided by engineer. @@ -6668,7 +7850,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This chat is protected by quantum resistant end-to-end encryption. - Ez a csevegés végpontok közötti kvantumrezisztens tikosítással védett. + Ez a csevegés végpontok közötti kvantumbiztos titkosítással védett. E2EE info chat item @@ -6678,12 +7860,12 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This display name is invalid. Please choose another name. - Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet. + Ez a megjelenítendő név érvénytelen. Válasszon egy másik nevet. No comment provided by engineer. This group has over %lld members, delivery receipts are not sent. - Ennek a csoportnak több mint %lld tagja van, a kézbesítési jelentések nem kerülnek elküldésre. + Ennek a csoportnak több mint %lld tagja van, a kézbesítési jelentések nem lesznek elküldve. No comment provided by engineer. @@ -6693,22 +7875,32 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This is your own SimpleX address! - Ez az ön SimpleX címe! + Ez a saját SimpleX-címe! No comment provided by engineer. This is your own one-time link! - Ez az egyszer használatos hivatkozása! + Ez a saját egyszer használható meghívója! + 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. No comment provided by engineer. This link was used with another mobile device, please create a new link on the desktop. - Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. + 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. + No comment provided by engineer. + + + This message was deleted or not received yet. + Ez az üzenet törölve lett vagy még nem érkezett meg. No comment provided by engineer. This setting applies to messages in your current chat profile **%@**. - Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes. + Ez a beállítás csak az Ön jelenlegi **%@** nevű csevegési profiljában lévő üzenetekre vonatkozik. No comment provided by engineer. @@ -6723,7 +7915,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. To connect, your contact can scan QR code or use the link in the app. - A kapcsolódáshoz az ismerőse beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást. + A kapcsolódáshoz a partnere beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást. No comment provided by engineer. @@ -6736,28 +7928,48 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Új kapcsolat létrehozásához No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX üzenetsorokhoz rendel azonosítókat, minden egyes ismerőshöz egy különbözőt. + + 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. No comment provided by engineer. To protect timezone, image/voice files use UTC. - Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. + Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak. No comment provided by engineer. To protect your IP address, private routing uses your SMP servers to deliver messages. - Az IP-cím védelmének érdekében a privát útválasztás az SMP kiszolgálókat használja az üzenetek kézbesítéséhez. + Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. No comment provided by engineer. To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. - A biztonsága érdekében kapcsolja be a SimpleX zár funkciót. + A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót. A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. + No comment provided by engineer. + + + To receive + A fogadáshoz + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + A beszéd rögzítéséhez adjon engedélyt a Mikrofon használatára. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + A videó rögzítéséhez adjon engedélyt a Kamera használatára. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Hangüzenet rögzítéséhez adjon engedélyt a mikrofon használathoz. @@ -6765,29 +7977,44 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. - Rejtett profilja megjelenítéséhez írja be a teljes jelszavát a keresőmezőbe a **Csevegési profilok** menüben. + Rejtett profilja felfedéséhez adja meg a teljes jelszót a keresőmezőben, a **Csevegési profilok** menüben. + No comment provided by engineer. + + + To send + A küldéshez No comment provided by engineer. To support instant push notifications the chat database has to be migrated. - Az azonnali push értesítések támogatásához a csevegési adatbázis migrálása szükséges. + Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges. + No comment provided by engineer. + + + To use the servers of **%@**, accept conditions of use. + A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket. 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) az ismerőse eszközén lévő kóddal. + A végpontok közötti titkosítás hitelesíté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őlista átváltása: + Csevegési lista átváltása: No comment provided by engineer. Toggle incognito when connecting. - Inkognitó mód kapcsolódáskor. + Inkognitóra váltás kapcsolódáskor. No comment provided by engineer. + + Token status: %@. + Token állapota: %@. + token status + Toolbar opacity Eszköztár átlátszatlansága @@ -6795,12 +8022,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Total - Összesen + Összes kapcsolat No comment provided by engineer. Transport isolation - Kapcsolat izolációs mód + Átvitel-izoláció No comment provided by engineer. @@ -6810,12 +8037,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Trying to connect to the server used to receive messages from this contact (error: %@). - Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %@). + Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (hiba: %@). No comment provided by engineer. Trying to connect to the server used to receive messages from this contact. - Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál. + Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál. No comment provided by engineer. @@ -6845,7 +8072,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unblock for all - Letiltás feloldása mindenki számára + Feloldás No comment provided by engineer. @@ -6855,12 +8082,17 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unblock member for all? - Mindenki számára feloldja a tag letiltását? + Az összes tag számára feloldja a tag letiltását? No comment provided by engineer. Unblock member? - Tag feloldása? + Feloldja a tag letiltását? + No comment provided by engineer. + + + Undelivered messages + Kézbesítetlen üzenetek No comment provided by engineer. @@ -6870,7 +8102,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unfav. - Nem kedvelt. + Kedvenc megszüntetése swipe action @@ -6911,7 +8143,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unknown servers! Ismeretlen kiszolgálók! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6921,8 +8153,8 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll 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. - Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba – jelentse a problémát. -A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. + Hacsak a partnere nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát. +A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. No comment provided by engineer. @@ -6932,7 +8164,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Unlink desktop? - Számítógép szétkapcsolása? + Leválasztja a számítógépet? No comment provided by engineer. @@ -6947,17 +8179,22 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Unmute - Némítás feloldása - swipe action + Némítás megszüntetése + notification label action Unread Olvasatlan swipe action + + 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 kerül elküldésre az új tagok számára. + Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára. No comment provided by engineer. @@ -6967,17 +8204,22 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Update database passphrase - Adatbázis jelmondat megváltoztatása + Az adatbázis jelmondatának módosítása No comment provided by engineer. Update network settings? - Hálózati beállítások megváltoztatása? + Módosítja a hálózati beállításokat? No comment provided by engineer. Update settings? - Beállítások frissítése? + Frissíti a beállításokat? + No comment provided by engineer. + + + Updated conditions + Frissített feltételek No comment provided by engineer. @@ -7020,19 +8262,39 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Archívum feltöltése No comment provided by engineer. + + Use %@ + %@ használata + No comment provided by engineer. + Use .onion hosts - Tor .onion kiszolgálók használata + Onion-kiszolgálók használata + No comment provided by engineer. + + + Use SOCKS proxy + SOCKS-proxy használata No comment provided by engineer. Use SimpleX Chat servers? - SimpleX Chat kiszolgálók használata? + SimpleX Chat-kiszolgálók használata? + No comment provided by engineer. + + + Use TCP port %@ when no port is specified. + A következő TCP-port használata, amikor nincs port megadva: %@. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz. No comment provided by engineer. Use chat - Csevegés használata + SimpleX Chat használata No comment provided by engineer. @@ -7040,6 +8302,16 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Jelenlegi profil használata No comment provided by engineer. + + Use for files + Használat a fájlokhoz + No comment provided by engineer. + + + Use for messages + Használat az üzenetekhez + No comment provided by engineer. + Use for new connections Alkalmazás új kapcsolatokhoz @@ -7047,17 +8319,17 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Use from desktop - Használat számítógépről + Társítás számítógéppel No comment provided by engineer. Use iOS call interface - Az iOS hívófelület használata + Az iOS hívási felületét használata No comment provided by engineer. Use new incognito profile - Az új inkognító profil használata + Új inkognitóprofil használata No comment provided by engineer. @@ -7067,7 +8339,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Use private routing with unknown servers when IP address is not protected. - Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett. No comment provided by engineer. @@ -7080,6 +8352,16 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Kiszolgáló használata No comment provided by engineer. + + Use servers + Kiszolgálók használata + No comment provided by engineer. + + + Use short links (BETA) + Rövid hivatkozások használata (béta) + No comment provided by engineer. + Use the app while in the call. Használja az alkalmazást hívás közben. @@ -7090,54 +8372,59 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Használja az alkalmazást egy kézzel. No comment provided by engineer. - - User profile - Felhasználói profil + + Use web port + Webport használata No comment provided by engineer. User selection - Felhasználó kiválasztása + Felhasználó kijelölése + No comment provided by engineer. + + + Username + Felhasználónév No comment provided by engineer. Using SimpleX Chat servers. - SimpleX Chat kiszolgálók használatban. + SimpleX Chat-kiszolgálók használatban. No comment provided by engineer. Verify code with desktop - Kód ellenőrzése a számítógépen + Kód hitelesítése a számítógépen No comment provided by engineer. Verify connection - Kapcsolat ellenőrzése + Kapcsolat hitelesítése No comment provided by engineer. Verify connection security - Kapcsolat biztonságának ellenőrzése + Biztonságos kapcsolat hitelesítése No comment provided by engineer. Verify connections - Kapcsolatok ellenőrzése + Kapcsolatok hitelesítése No comment provided by engineer. Verify database passphrase - Adatbázis jelmondatának ellenőrzése + Az adatbázis jelmondatának hitelesítése No comment provided by engineer. Verify passphrase - Jelmondat ellenőrzése + Jelmondat hitelesítése No comment provided by engineer. Verify security code - Biztonsági kód ellenőrzése + Biztonsági kód hitelesítése No comment provided by engineer. @@ -7147,7 +8434,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Via secure quantum resistant protocol. - Biztonságos kvantum ellenálló protokoll által. + Biztonságos kvantumbiztos protokollon keresztül. No comment provided by engineer. @@ -7167,7 +8454,12 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Videos and files up to 1gb - Videók és fájlok 1Gb méretig + Videók és fájlok legfeljebb 1GB méretig + No comment provided by engineer. + + + View conditions + Feltételek megtekintése No comment provided by engineer. @@ -7175,6 +8467,11 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Biztonsági kód megtekintése No comment provided by engineer. + + View updated conditions + Frissített feltételek megtekintése + No comment provided by engineer. + Visible history Látható előzmények @@ -7190,9 +8487,9 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol A hangüzenetek küldése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Voice messages are prohibited in this group. - A hangüzenetek küldése le van tiltva ebben a csoportban. + + Voice messages are prohibited. + A hangüzenetek küldése le van tiltva. No comment provided by engineer. @@ -7202,7 +8499,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Voice messages prohibited! - A hangüzenetek le vannak tilva! + A hangüzenetek le vannak tiltva! No comment provided by engineer. @@ -7212,27 +8509,27 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Waiting for desktop... - Várakozás az asztali kliensre... + Várakozás a számítógép-alkalmazásra… No comment provided by engineer. Waiting for file - Fájlra várakozás + Várakozás a fájlra No comment provided by engineer. Waiting for image - Képre várakozás + Várakozás a képre No comment provided by engineer. Waiting for video - Videóra várakozás + Várakozás a videóra No comment provided by engineer. Wallpaper accent - Háttérkép kiemelés + Háttérkép kiemelőszíne No comment provided by engineer. @@ -7242,7 +8539,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Warning: starting chat on multiple devices is not supported and will cause message delivery failures - Figyelmeztetés: a csevegés elindítása egyszerre több eszközön nem támogatott, továbbá üzenetkézbesítési hibákat okozhat + Figyelmeztetés: a csevegés elindítása egyszerre több eszközön nem támogatott, mert üzenetkézbesítési hibákat okoz No comment provided by engineer. @@ -7252,27 +8549,27 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol WebRTC ICE servers - WebRTC ICE kiszolgálók + WebRTC ICE-kiszolgálók No comment provided by engineer. Welcome %@! - Üdvözöllek %@! + Üdvözöljük %@! No comment provided by engineer. Welcome message - Üdvözlő üzenet + Üdvözlőüzenet No comment provided by engineer. Welcome message is too long - Az üdvözlő üzenet túl hosszú + Az üdvözlőüzenet túl hosszú No comment provided by engineer. What's new - Milyen újdonságok vannak + Újdonságok No comment provided by engineer. @@ -7285,9 +8582,9 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Amikor egy bejövő hang- vagy videóhívás érkezik. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Amikor egynél több üzemeltető van engedélyezve, akkor egyik sem rendelkezik olyan metaadatokkal, amelyekből megtudható, hogy ki kivel kommunikál. No comment provided by engineer. @@ -7312,47 +8609,47 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol With encrypted files and media. - Titkosított fájlokkal és médiatartalommal. + Titkosított fájlokkal és médiatartalmakkal. No comment provided by engineer. With optional welcome message. - Opcionális üdvözlő üzenettel. + Nem kötelező üdvözlőüzenettel. No comment provided by engineer. With reduced battery usage. - Csökkentett akkumulátorhasználattal. + Csökkentett akkumulátor-használattal. No comment provided by engineer. Without Tor or VPN, your IP address will be visible to file servers. - Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára. + Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára. No comment provided by engineer. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - Tor vagy VPN nélkül az IP-címe látható lesz ezen XFTP átjátszók számára: %@. - No comment provided by engineer. + Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@. + alert message Wrong database passphrase - Hibás adatbázis jelmondat + Érvénytelen adatbázis-jelmondat No comment provided by engineer. Wrong key or unknown connection - most likely this connection is deleted. - Hibás kulcs vagy ismeretlen kapcsolat - valószínűleg ez a kapcsolat törlődött. + Érvénytelen kulcs vagy ismeretlen kapcsolat – valószínűleg ez a kapcsolat törlődött. snd error text Wrong key or unknown file chunk address - most likely file is deleted. - Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. + Érvénytelen kulcs vagy ismeretlen fájltöredékcím – valószínűleg a fájl törlődött. file error text Wrong passphrase! - Hibás jelmondat! + Érvénytelen jelmondat! No comment provided by engineer. @@ -7360,11 +8657,6 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol XFTP-kiszolgáló No comment provided by engineer. - - You - Ön - No comment provided by engineer. - You **must not** use the same database on two devices. **Nem szabad** ugyanazt az adatbázist használni egyszerre két eszközön. @@ -7377,37 +8669,42 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol You allow - Engedélyezte + Ön engedélyezi No comment provided by engineer. You already have a chat profile with the same display name. Please choose another name. - Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet. + Már van egy csevegési profil ugyanezzel a megjelenítendő névvel. Válasszon egy másik nevet. No comment provided by engineer. You are already connected to %@. - Már kapcsolódva van hozzá: %@. + Ön már kapcsolódott a következőhöz: %@. + No comment provided by engineer. + + + You are already connected with %@. + Ön már kapcsolódva van vele: %@. No comment provided by engineer. You are already connecting to %@. - Már folyamatban van a kapcsolódás ehhez: %@. + A kapcsolódás már folyamatban van a következőhöz: %@. No comment provided by engineer. You are already connecting via this one-time link! - A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható meghívón keresztül! No comment provided by engineer. You are already in group %@. - Már a(z) %@ csoport tagja. + Ön már a(z) %@ nevű csoport tagja. No comment provided by engineer. You are already joining the group %@. - A csatlakozás már folyamatban van a(z) %@ csoporthoz. + A csatlakozás már folyamatban van a(z) %@ nevű csoporthoz. No comment provided by engineer. @@ -7423,18 +8720,18 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol You are already joining the group! Repeat join request? - Csatlakozás folyamatban! -Csatlakozási kérés megismétlése? + A csatlakozás már folyamatban van a csoporthoz! +Megismétli a meghívási kérést? No comment provided by engineer. You are connected to the server used to receive messages from this contact. - Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál. + Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál. No comment provided by engineer. You are invited to group - Meghívást kapott a csoportba + Ön meghívást kapott a csoportba No comment provided by engineer. @@ -7444,7 +8741,7 @@ Csatlakozási kérés megismétlése? You can accept calls from lock screen, without device and app authentication. - Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazáshitelesítés nélkül. + Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazás-hitelesítés nélkül. No comment provided by engineer. @@ -7452,6 +8749,11 @@ Csatlakozási kérés megismétlése? Ezt a „Megjelenés” menüben módosíthatja. No comment provided by engineer. + + You can configure servers via settings. + A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja. + No comment provided by engineer. + You can create it later Létrehozás később @@ -7459,12 +8761,12 @@ Csatlakozási kérés megismétlése? You can enable later via Settings - Később engedélyezheti a Beállításokban + Később engedélyezheti a „Beállításokban” No comment provided by engineer. You can enable them later via app Privacy & Security settings. - Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben. + Később engedélyezheti őket az „Adatvédelem és biztonság” menüben. No comment provided by engineer. @@ -7474,12 +8776,12 @@ Csatlakozási kérés megismétlése? You can hide or mute a user profile - swipe it to the right. - Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt. + Elrejtheti vagy lenémíthatja a felhasználó -profiljait – csúsztassa jobbra a profilt. No comment provided by engineer. You can make it visible to your SimpleX contacts via Settings. - Láthatóvá teheti SimpleX ismerősök számára a Beállításokban. + Láthatóvá teheti a SimpleXbeli partnerei számára a „Beállításokban”. No comment provided by engineer. @@ -7489,42 +8791,42 @@ Csatlakozási kérés megismétlése? You can send messages to %@ from Archived contacts. - Az „Archivált ismerősökből” továbbra is küldhet üzeneteket neki: %@. + Az „Archivált partnerekből” továbbra is küldhet üzeneteket neki: %@. + No comment provided by engineer. + + + You can set connection name, to remember who the link was shared with. + Beállíthatja a partner nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást. No comment provided by engineer. You can set lock screen notification preview via settings. - A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét. + 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 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 csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. + Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoportot Ön később törli, akkor nem fogja elveszíteni annak tagjait. No comment provided by engineer. You can share this address with your contacts to let them connect with **%@**. - Megoszthatja ezt a címet az ismerőseivel, hogy kapcsolatba léphessenek önnel a(z) **%@** nevű profilján keresztül. - No comment provided by engineer. - - - You can share your address as a link or QR code - anybody can connect to you. - Megoszthatja a címét egy hivatkozásként vagy QR-kódként – így bárki kapcsolódhat önhöz. + Megoszthatja ezt a SimpleX-címet a partnereivel, hogy kapcsolatba léphessenek vele: **%@**. No comment provided by engineer. You can start chat via app Settings / Database or by restarting the app - A csevegést az alkalmazás Beállítások / Adatbázis menüben vagy az alkalmazás újraindításával indíthatja el + A csevegést az alkalmazás „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával indíthatja el No comment provided by engineer. You can still view conversation with %@ in the list of chats. - A(z) %@ nevű ismerősével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában. + A(z) %@ nevű partnerével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában. No comment provided by engineer. You can turn on SimpleX Lock via Settings. - A SimpleX zár az „Adatvédelem és biztonság” menüben kapcsolható be. + A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be. No comment provided by engineer. @@ -7534,54 +8836,54 @@ Csatlakozási kérés megismétlése? You can view invitation link again in connection details. - A meghívó hivatkozást újra megtekintheti a kapcsolat részleteinél. - No comment provided by engineer. + A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél. + alert message You can't send messages! Nem lehet üzeneteket küldeni! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt kiszolgálókon. - No comment provided by engineer. - You could not be verified; please try again. - Nem lehetett ellenőrizni; próbálja meg újra. + Nem sikerült hitelesíteni; 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 via this address! - Már kért egy kapcsolódási kérelmet ezen a címen keresztül! + Már küldött egy meghívási kérést ezen a címen keresztül! No comment provided by engineer. You have already requested connection! Repeat connection request? - Már kért egy kapcsolódási kérelmet! -Kapcsolódási kérés megismétlése? + Ön már küldött egy meghívási kérést! +Megismétli a meghívási kérést? No comment provided by engineer. You have to enter passphrase every time the app starts - it is not stored on the device. - A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. + A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul – nem az eszközön van tárolva. No comment provided by engineer. You invited a contact - Meghívta egy ismerősét + Ön meghívta egy partnerét No comment provided by engineer. You joined this group - Csatlakozott ehhez a csoporthoz + Ön csatlakozott ehhez a csoporthoz No comment provided by engineer. You joined this group. Connecting to inviting group member. - Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. + Ön csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. No comment provided by engineer. @@ -7596,29 +8898,34 @@ Kapcsolódási kérés megismétlése? 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. - A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerőstől. + A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi partnerétől. No comment provided by engineer. You need to allow your contact to call to be able to call them. - Engedélyeznie kell a hívásokat az ismerőse számára, hogy fel tudják hívni egymást. + Engedélyeznie kell a hívásokat a partnere számára, hogy fel tudják hívni egymást. No comment provided by engineer. You need to allow your contact to send voice messages to be able to send them. - Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősök számára. + Engedélyeznie kell a hangüzenetek küldését a partnere számára, hogy hangüzeneteket küldhessenek egymásnak. No comment provided by engineer. You rejected group invitation - Csoport meghívó elutasítva + Csoportmeghívó elutasítva No comment provided by engineer. You sent group invitation - Csoport meghívó elküldve + Csoportmeghívó elküldve No comment provided by engineer. + + You should receive notifications. + Ön megkapja az értesítéseket. + token info + You will be connected to group when the group host's device is online, please wait or check later! Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! @@ -7626,22 +8933,22 @@ Kapcsolódási kérés megismétlése? You will be connected when group link host's device is online, please wait or check later! - Akkor lesz kapcsolódva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, amikor a csoporthivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! No comment provided by engineer. You will be connected when your connection request is accepted, please wait or check later! - Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később! No comment provided by engineer. You will be connected when your contact's device is online, please wait or check later! - Akkor le kapcsolódva, amikor az ismerőse eszköze online lesz, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, amikor a partnerének az eszköze online lesz, várjon, vagy ellenőrizze később! No comment provided by engineer. You will be required to authenticate when you start or resume the app after 30 seconds in background. - Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges. + Az alkalmazás elindításához vagy 30 másodpercnyi háttérben töltött idő után, az alkalmazáshoz való visszatéréshez hitelesítésre lesz szükség. No comment provided by engineer. @@ -7654,6 +8961,11 @@ Kapcsolódási kérés megismétlése? 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 chat. Chat history will be preserved. + Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak. @@ -7661,42 +8973,27 @@ Kapcsolódási kérés megismétlése? You won't lose your contacts if you later delete your address. - Nem veszíti el az ismerőseit, ha később törli a címét. + Nem veszíti el a partnereit, ha később törli a címét. 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 - Egy olyan ismerőst próbál meghívni, akivel inkognító profilt osztott meg abban a csoportban, amelyben saját fő profilja van használatban + Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a fő profilja van használatban 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 - Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében meghívók küldése tiltott - No comment provided by engineer. - - - Your %@ servers - %@ nevű profiljához tartozó kiszolgálók + Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva No comment provided by engineer. Your ICE servers - ICE kiszolgálók - No comment provided by engineer. - - - Your SMP servers - SMP kiszolgálók + Saját ICE-kiszolgálók No comment provided by engineer. Your SimpleX address - Az ön SimpleX címe - No comment provided by engineer. - - - Your XFTP servers - XFTP kiszolgálók + Profil SimpleX-címe No comment provided by engineer. @@ -7714,29 +9011,44 @@ Kapcsolódási kérés megismétlése? A csevegési adatbázis nincs titkosítva – adjon meg egy jelmondatot a titkosításhoz. No comment provided by engineer. + + Your chat preferences + Az Ön csevegési beállításai + alert title + Your chat profiles Csevegési profilok No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + A kapcsolata át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányításkor. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). - Ismerőse olyan fájlt küldött, amely meghaladja a jelenleg támogatott maximális méretet (%@). + A partnere a jelenleg megengedett maximális méretű (%@) fájlnál nagyobbat küldött. No comment provided by engineer. Your contacts can allow full message deletion. - Ismerősök engedélyezhetik a teljes üzenet törlést. + A partnerei engedélyezhetik a teljes üzenet törlését. No comment provided by engineer. Your contacts will remain connected. - Az ismerősei továbbra is kapcsolódva maradnak. + A partnerei továbbra is kapcsolódva maradnak. + No comment provided by engineer. + + + Your credentials may be sent unencrypted. + A hitelesítőadatai titkosítatlanul is elküldhetők. No comment provided by engineer. Your current chat database will be DELETED and REPLACED with the imported one. - A jelenlegi csevegési adatbázis TÖRLŐDNI FOG, és a HELYÉRE az importált adatbázis kerül. + A jelenlegi csevegési adatbázis TÖRÖLVE és CSERÉLVE lesz az importáltra. No comment provided by engineer. @@ -7761,34 +9073,37 @@ Kapcsolódási kérés megismétlése? Your profile **%@** will be shared. - A(z) **%@** nevű profilja megosztásra fog kerülni. + A(z) **%@** nevű profilja meg lesz osztva. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. -A SimpleX kiszolgálók nem látjhatják profilját. + + Your profile is stored on your device and only shared with your contacts. + A profilja csak a partnereivel van megosztva. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Profilja, ismerősei és az elküldött üzenetei az eszközön kerülnek tárolásra. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára. + alert message + Your random profile Véletlenszerű profil No comment provided by engineer. - - Your server - Saját kiszolgáló - No comment provided by engineer. - Your server address - Saját kiszolgáló cím + Saját SMP-kiszolgálójának címe + No comment provided by engineer. + + + Your servers + Saját kiszolgálók No comment provided by engineer. @@ -7798,7 +9113,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Hozzájárulás](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. @@ -7808,7 +9123,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat) + [Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. @@ -7828,22 +9143,27 @@ A SimpleX kiszolgálók nem látjhatják profilját. accepted call - elfogadott hívás + fogadott hívás call status + + accepted invitation + elfogadott meghívó + chat list item title + admin - admin + adminisztrátor member role admins - adminok + adminisztrátorok feature role agreeing encryption for %@… - titkosítás jóváhagyása %@ számára… + titkosítás elfogadása %@ számára… chat item text @@ -7853,7 +9173,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. all members - minden tag + összes tag feature role @@ -7866,6 +9186,11 @@ A SimpleX kiszolgálók nem látjhatják profilját. és további %lld esemény No comment provided by engineer. + + archived report + archivált jelentés + No comment provided by engineer. + attempts próbálkozások @@ -7888,7 +9213,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. bad message hash - hibás az üzenet ellenőrzőösszege + érvénytelen az üzenet hasítóértéke integrity error chat item @@ -7898,13 +9223,14 @@ A SimpleX kiszolgálók nem látjhatják profilját. blocked %@ - letiltotta %@-t + letiltotta őt: %@ rcv group event chat item blocked by admin - letiltva az admin által - marked deleted chat item preview text + letiltva az adminisztrátor által + blocked chat item +marked deleted chat item preview text bold @@ -7918,7 +9244,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. call error - hiba a hívásban + híváshiba call status @@ -7938,32 +9264,32 @@ A SimpleX kiszolgálók nem látjhatják profilját. changed address for you - cím megváltoztatva + módosította a címet az Ön számára chat item text changed role of %1$@ to %2$@ - %1$@ szerepkörét megváltoztatta erre: %2$@ + a következőre módosította %1$@ szerepkörét: „%2$@” rcv group event chat item changed your role to %@ - megváltoztatta a szerepkörét erre: %@ + a következőre módosította az Ön szerepkörét: „%@” rcv group event chat item changing address for %@… - cím megváltoztatása nála: %@… + cím módosítása %@ számára… chat item text changing address… - cím megváltoztatása… + cím módosítása… chat item text colored - színes + színezett No comment provided by engineer. @@ -7973,17 +9299,17 @@ A SimpleX kiszolgálók nem látjhatják profilját. connect to SimpleX Chat developers. - Kapcsolódás a SimpleX Chat fejlesztőkhöz. + kapcsolódás a SimpleX Chat fejlesztőkhöz. No comment provided by engineer. connected - kapcsolódva + kapcsolódott No comment provided by engineer. connected directly - közvetlenül kapcsolódva + közvetlenül kapcsolódott rcv group event chat item @@ -8003,7 +9329,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. connecting (introduced) - kapcsolódás (bejelentve) + kapcsolódás (bemutatkozva) No comment provided by engineer. @@ -8013,13 +9339,13 @@ A SimpleX kiszolgálók nem látjhatják profilját. connecting call… - hívás kapcsolódik… + kapcsolódási hívás… call status connecting… kapcsolódás… - chat list item title + No comment provided by engineer. connection established @@ -8033,17 +9359,17 @@ A SimpleX kiszolgálók nem látjhatják profilját. contact %1$@ changed to %2$@ - %1$@ megváltoztatta a nevét erre: %2$@ + %1$@ a következőre módosította a nevét: %2$@ profile update event chat item contact has e2e encryption - az ismerősnél az e2e titkosítás elérhető + a partner e2e titkosítással rendelkezik No comment provided by engineer. contact has no e2e encryption - az ismerősnél az e2e titkosítás nem elérhető + a partner nem rendelkezik e2e titkosítással No comment provided by engineer. @@ -8053,12 +9379,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. custom - egyedi + egyéni dropdown time picker choice database version is newer than the app, but no down migration for: %@ - az adatbázis verziója újabb, mint az alkalmazásé, de nincs visszafelé átköltöztetés ehhez: %@ + az adatbázis verziója újabb, mint az alkalmazásé, de a visszafelé történő átköltöztetés viszont nem lehetséges a következőhöz: %@ No comment provided by engineer. @@ -8074,7 +9400,8 @@ A SimpleX kiszolgálók nem látjhatják profilját. default (%@) alapértelmezett (%@) - pref value + delete after time +pref value default (no) @@ -8093,7 +9420,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. deleted contact - törölt ismerős + törölt partner rcv direct event chat item @@ -8103,7 +9430,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. different migration in the app/database: %@ / %@ - különböző átköltöztetések az alkalmazásban/adatbázisban: %@ / %@ + különböző átköltöztetés az alkalmazásban/adatbázisban: %@ / %@ No comment provided by engineer. @@ -8138,17 +9465,17 @@ A SimpleX kiszolgálók nem látjhatják profilját. enabled for contact - engedélyezve az ismerős számára + engedélyezve a partner számára enabled status enabled for you - engedélyezve az ön számára + engedélyezve az Ön számára enabled status encryption agreed - titkosítás egyeztetve + titkosítás elfogadva chat item text @@ -8163,27 +9490,27 @@ A SimpleX kiszolgálók nem látjhatják profilját. encryption ok for %@ - titkosítás rendben vele: %@ + titkosítás rendben %@ számára chat item text encryption re-negotiation allowed - titkosítás újraegyeztetés engedélyezve + a titkosítás újraegyeztetése engedélyezve van chat item text encryption re-negotiation allowed for %@ - titkosítás újraegyeztetés engedélyezve vele: %@ + a titkosítás újraegyeztetése engedélyezve van %@ számára chat item text encryption re-negotiation required - titkosítás újraegyeztetés szükséges + a titkosítás újraegyeztetése szükséges chat item text encryption re-negotiation required for %@ - titkosítás újraegyeztetés szükséges %@ számára + a titkosítás újraegyeztetése szükséges %@ számára chat item text @@ -8201,11 +9528,6 @@ A SimpleX kiszolgálók nem látjhatják profilját. hiba No comment provided by engineer. - - event happened - esemény történt - No comment provided by engineer. - expired lejárt @@ -8233,12 +9555,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. iOS Keychain is used to securely store passphrase - it allows receiving push notifications. - Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását. + Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a push-értesítések fogadását. 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. - Az iOS kulcstartó az alkalmazás újraindítása, vagy a jelmondat módosítása után a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását. + Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a push-értesítések fogadását. No comment provided by engineer. @@ -8248,17 +9570,17 @@ A SimpleX kiszolgálók nem látjhatják profilját. incognito via contact address link - inkognitó a kapcsolattartási hivatkozáson keresztül + inkognitó a kapcsolattartási címhivatkozáson keresztül chat list item description incognito via group link - inkognitó a csoportos hivatkozáson keresztül + inkognitó a csoporthivatkozáson keresztül chat list item description incognito via one-time link - inkognitó az egyszer használatos hivatkozáson keresztül + inkognitó egy egyszer használható meghívón keresztül chat list item description @@ -8273,7 +9595,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. invalid chat data - érvénytelen csevegés adat + érvénytelen csevegésadat No comment provided by engineer. @@ -8303,12 +9625,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. invited to connect - meghívta, hogy csatlakozzon + Függőben lévő meghívó chat list item title invited via your group link - meghíva az ön csoport hivatkozásán keresztül + meghíva a saját csoporthivatkozásán keresztül rcv group event chat item @@ -8318,7 +9640,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. join as %@ - csatlakozás mint: %@ + csatlakozás mint %@ No comment provided by engineer. @@ -8328,7 +9650,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. marked deleted - töröltnek jelölve + törlésre jelölve marked deleted chat item preview text @@ -8338,7 +9660,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. member %1$@ changed to %2$@ - %1$@ megváltoztatta a nevét erre: %2$@ + %1$@ a következőre módosította a nevét: %2$@ profile update event chat item @@ -8376,20 +9698,20 @@ A SimpleX kiszolgálók nem látjhatják profilját. moderálva lett %@ által marked deleted chat item preview text + + moderator + moderátor + member role + months hónap time unit - - mute - némítás - No comment provided by engineer. - never soha - No comment provided by engineer. + delete after time new message @@ -8420,8 +9742,8 @@ A SimpleX kiszolgálók nem látjhatják profilját. off kikapcsolva enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8430,7 +9752,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. offered %1$@: %2$@ - ajánlotta %1$@: %2$@-kor + ajánlotta: %1$@, ekkor: %2$@ feature offered item @@ -8460,17 +9782,27 @@ A SimpleX kiszolgálók nem látjhatják profilját. peer-to-peer - ponttól-pontig + egyenrangú + No comment provided by engineer. + + + pending + függőben + No comment provided by engineer. + + + pending approval + jóváhagyásra vár No comment provided by engineer. quantum resistant e2e encryption - végpontok közötti kvantumrezisztens titkosítás + végpontok közötti kvantumbiztos titkosítás chat item text received answer… - fogadott válasz… + válasz fogadása… No comment provided by engineer. @@ -8478,6 +9810,11 @@ A SimpleX kiszolgálók nem látjhatják profilját. visszaigazolás fogadása… No comment provided by engineer. + + rejected + elutasítva + No comment provided by engineer. + rejected call elutasított hívás @@ -8495,19 +9832,24 @@ A SimpleX kiszolgálók nem látjhatják profilját. removed contact address - törölt kapcsolattartási cím + eltávolította a kapcsolattartási címet profile update event chat item removed profile picture - törölte a profilképét + eltávolította a profilképét profile update event chat item removed you - eltávolítottak + eltávolította Önt rcv group event chat item + + requested to connect + Függőben lévő meghívási kérelem + chat list item title + saved mentett @@ -8515,7 +9857,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. saved from %@ - mentve innen: %@ + elmentve innen: %@ No comment provided by engineer. @@ -8540,7 +9882,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. security code changed - a biztonsági kód megváltozott + a biztonsági kód módosult chat item text @@ -8552,14 +9894,14 @@ A SimpleX kiszolgálók nem látjhatják profilját. server queue info: %1$@ last received msg: %2$@ - kiszolgáló üzenet-várakotatási információ: %1$@ + a kiszolgáló sorbaállítási információi: %1$@ utoljára fogadott üzenet: %2$@ queue info set new contact address - új kapcsolattartási cím beállítása + új kapcsolattartási címet állított be profile update event chat item @@ -8584,7 +9926,7 @@ utoljára fogadott üzenet: %2$@ this contact - ez az ismerős + ez a partner notification title @@ -8604,12 +9946,7 @@ utoljára fogadott üzenet: %2$@ unknown status - ismeretlen státusz - No comment provided by engineer. - - - unmute - némítás feloldása + ismeretlen állapot No comment provided by engineer. @@ -8639,22 +9976,22 @@ utoljára fogadott üzenet: %2$@ via contact address link - kapcsolattartási cím-hivatkozáson keresztül + a kapcsolattartási címhivatkozáson keresztül chat list item description via group link - csoport hivatkozáson keresztül + a csoporthivatkozáson keresztül chat list item description via one-time link - egyszer használatos hivatkozáson keresztül + egy egyszer használható meghívón keresztül chat list item description via relay - átjátszón keresztül + egy továbbítókiszolgálón keresztül No comment provided by engineer. @@ -8679,7 +10016,7 @@ utoljára fogadott üzenet: %2$@ wants to connect to you! - kapcsolatba akar lépni önnel! + kapcsolatba akar lépni Önnel! No comment provided by engineer. @@ -8699,72 +10036,72 @@ utoljára fogadott üzenet: %2$@ you - ön + Ön No comment provided by engineer. you are invited to group - meghívást kapott a csoportba + Ön meghívást kapott a csoportba No comment provided by engineer. you are observer - megfigyelő szerep + Ön megfigyelő No comment provided by engineer. you blocked %@ - ön letiltotta %@-t + Ön letiltotta őt: %@ snd group event chat item you changed address - cím megváltoztatva + Ön módosította a címet chat item text you changed address for %@ - cím megváltoztatva nála: %@ + Ön módosította a címet %@ számára chat item text you changed role for yourself to %@ - saját szerepkör megváltoztatva erre: %@ + Ön a következőre módosította a saját szerepkörét: „%@” snd group event chat item you changed role of %1$@ to %2$@ - %1$@ szerepkörét megváltoztatta erre: %@ + Ön a következőre módosította %1$@ szerepkörét: „%2$@” snd group event chat item you left - elhagyta a csoportot + Ön elhagyta a csoportot snd group event chat item you removed %@ - eltávolította őt: %@ + Ön eltávolította őt: %@ snd group event chat item you shared one-time link - egyszer használatos hivatkozást osztott meg + Ön egy egyszer használható meghívót osztott meg chat list item description you shared one-time link incognito - egyszer használatos hivatkozást osztott meg inkognitóban + Ön egy egyszer használható meghívót osztott meg inkognitóban chat list item description you unblocked %@ - ön feloldotta %@ letiltását + Ön feloldotta %@ letiltását snd group event chat item you: - ön: + Ön: No comment provided by engineer. @@ -8776,7 +10113,7 @@ utoljára fogadott üzenet: %2$@
- +
@@ -8786,7 +10123,7 @@ utoljára fogadott üzenet: %2$@ SimpleX needs camera access to scan QR codes to connect to other users and for video calls. - A SimpleX-nek 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 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. Privacy - Camera Usage Description @@ -8796,24 +10133,24 @@ utoljára fogadott üzenet: %2$@ SimpleX uses local network access to allow using user chat profile via desktop app on the same network. - A SimpleX helyi hálózati hozzáférést használ, hogy lehetővé tegye a felhasználói csevegőprofil használatát számítógépen keresztül ugyanazon a hálózaton. + 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 - Local Network Usage Description SimpleX needs microphone access for audio and video calls, and to record voice messages. - A SimpleX-nek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. + A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. Privacy - Microphone Usage Description SimpleX needs access to Photo Library for saving captured and received media - A SimpleX-nek hozzáférésre van szüksége a Galériához a rögzített és fogadott média mentéséhez + A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez Privacy - Photo Library Additions Usage Description
- +
@@ -8833,9 +10170,41 @@ utoljára fogadott üzenet: %2$@
+ +
+ +
+ + + %d new events + %d új esemény + notification body + + + From %d chat(s) + %d csevegésből + notification body + + + From: %@ + Tőle: %@ + notification body + + + New events + Új események + notification + + + New messages + Új üzenetek + notification + + +
- +
@@ -8857,7 +10226,7 @@ utoljára fogadott üzenet: %2$@
- +
@@ -8867,7 +10236,7 @@ utoljára fogadott üzenet: %2$@ App is locked! - Az alkalmazás zárolva! + Az alkalmazás zárolva van! No comment provided by engineer. @@ -8892,7 +10261,7 @@ utoljára fogadott üzenet: %2$@ Currently maximum supported file size is %@. - Jelenleg a maximális támogatott fájlméret %@. + Jelenleg támogatott legnagyobb fájl méret: %@. No comment provided by engineer. @@ -8907,17 +10276,17 @@ utoljára fogadott üzenet: %2$@ Database error - Adatbázis hiba + Adatbázishiba No comment provided by engineer. Database passphrase is different from saved in the keychain. - Az adatbázis jelmondata eltér a kulcstartóban lévőtől. + Az adatbázis jelmondata nem egyezik a kulcstartóba mentettől. No comment provided by engineer. Database passphrase is required to open chat. - Adatbázis jelmondat szükséges a csevegés megnyitásához. + A csevegés megnyitásához adja meg az adatbázis jelmondatát. No comment provided by engineer. @@ -8927,12 +10296,12 @@ utoljára fogadott üzenet: %2$@ Error preparing file - Hiba a fájl előkészítésekor + Hiba történt a fájl előkészítésekor No comment provided by engineer. Error preparing message - Hiba az üzenet előkészítésekor + Hiba történt az üzenet előkészítésekor No comment provided by engineer. @@ -8947,7 +10316,7 @@ utoljára fogadott üzenet: %2$@ Incompatible database version - Nem kompatibilis adatbázis verzió + Nem kompatibilis adatbázis-verzió No comment provided by engineer. @@ -8957,7 +10326,7 @@ utoljára fogadott üzenet: %2$@ Keychain error - Kulcstartó hiba + Kulcstartóhiba No comment provided by engineer. @@ -8997,7 +10366,7 @@ utoljára fogadott üzenet: %2$@ Selected chat preferences prohibit this message. - A kiválasztott csevegési beállítások tiltják ezt az üzenetet. + A kijelölt csevegési beállítások tiltják ezt az üzenetet. No comment provided by engineer. @@ -9017,12 +10386,12 @@ utoljára fogadott üzenet: %2$@ Slow network? - Lassú internetkapcsolat? + Lassú a hálózata? No comment provided by engineer. Unknown database error: %@ - Ismeretlen adatbázis hiba: %@ + Ismeretlen adatbázishiba: %@ No comment provided by engineer. @@ -9037,12 +10406,12 @@ utoljára fogadott üzenet: %2$@ Wrong database passphrase - Hibás adatbázis jelmondat + Érvénytelen adatbázis-jelmondat No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. - A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti. + A megosztást az Adatvédelem és biztonság / SimpleX-zár menüben engedélyezheti. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/contents.json index 0b16198498..c07ec0f900 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/hu.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "hu", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 72eb3561e3..cf5f61918f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (può essere copiato) @@ -127,6 +100,16 @@ %@ è verificato/a No comment provided by engineer. + + %@ server + %@ server + No comment provided by engineer. + + + %@ servers + %@ server + No comment provided by engineer. + %@ uploaded %@ caricati @@ -137,6 +120,11 @@ %@ si vuole connettere! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ e %lld membri @@ -157,11 +145,36 @@ %d giorni time interval + + %d file(s) are still being downloaded. + %d file è/sono ancora in scaricamento. + forward confirmation reason + + + %d file(s) failed to download. + %d file ha/hanno fallito lo scaricamento. + forward confirmation reason + + + %d file(s) were deleted. + %d file è/sono stato/i eliminato/i. + forward confirmation reason + + + %d file(s) were not downloaded. + %d file non è/sono stato/i scaricato/i. + forward confirmation reason + %d hours %d ore time interval + + %d messages not forwarded + %d messaggi non inoltrati + alert title + %d min %d min @@ -177,6 +190,11 @@ %d sec time interval + + %d seconds(s) + %d secondo/i + delete after time + %d skipped message(s) %d messaggio/i saltato/i @@ -247,11 +265,6 @@ %lld nuove lingue dell'interfaccia No comment provided by engineer. - - %lld second(s) - %lld secondo/i - No comment provided by engineer. - %lld seconds %lld secondi @@ -302,11 +315,6 @@ %u messaggi saltati. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nuovo) @@ -317,19 +325,9 @@ (questo dispositivo v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Aggiungi contatto**: per creare un nuovo link di invito o connetterti tramite un link che hai ricevuto. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Aggiungi un contatto**: per creare il tuo codice QR o link una tantum per il tuo contatto. + + **Create 1-time link**: to create and share a new invitation link. + **Aggiungi contatto**: per creare un nuovo link di invito. No comment provided by engineer. @@ -337,13 +335,13 @@ **Crea gruppo**: per creare un nuovo gruppo. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Più privato**: controlla messaggi nuovi ogni 20 minuti. Viene condiviso il token del dispositivo con il server di SimpleX Chat, ma non quanti contatti o messaggi hai. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app). No comment provided by engineer. @@ -357,11 +355,16 @@ **Nota bene**: NON potrai recuperare o cambiare la password se la perdi. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Consigliato**: vengono inviati il token del dispositivo e le notifiche al server di notifica di SimpleX Chat, ma non il contenuto del messaggio,la sua dimensione o il suo mittente. 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. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi. @@ -387,11 +390,6 @@ \*grassetto* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - cronologia delle modifiche. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -446,7 +439,8 @@ 1 day 1 giorno - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 mese - time interval + delete after time +time interval 1 week 1 settimana - time interval + delete after time +time interval + + + 1 year + 1 anno + delete after time + + + 1-time link + Link una tantum + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Il link una tantum può essere usato *con un solo contatto* - condividilo di persona o tramite qualsiasi messenger. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 secondi No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,19 +543,14 @@ Interrompere il cambio di indirizzo? No comment provided by engineer. - - About SimpleX - Riguardo SimpleX - No comment provided by engineer. - About SimpleX Chat Riguardo SimpleX Chat No comment provided by engineer. - - About SimpleX address - Info sull'indirizzo SimpleX + + About operators + Info sugli operatori No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Accetta accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Accetta le condizioni + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Accetta in incognito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Condizioni accettate + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Errori di riconoscimento No comment provided by engineer. + + Active + Attivo + token status text + Active connections Connessioni attive @@ -600,14 +616,14 @@ 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. No comment provided by engineer. - - Add contact - Aggiungi contatto + + Add friends + Aggiungi amici No comment provided by engineer. - - Add preset servers - Aggiungi server preimpostati + + Add list + Aggiungi elenco No comment provided by engineer. @@ -625,16 +641,41 @@ Aggiungi server scansionando codici QR. No comment provided by engineer. + + Add team members + Aggiungi membri del team + No comment provided by engineer. + Add to another device Aggiungi ad un altro dispositivo No comment provided by engineer. + + Add to list + Aggiungi ad un elenco + No comment provided by engineer. + Add welcome message Aggiungi messaggio di benvenuto No comment provided by engineer. + + Add your team members to the conversations. + Aggiungi i membri del tuo team alle conversazioni. + No comment provided by engineer. + + + Added media & file servers + Server di multimediali e file aggiunti + No comment provided by engineer. + + + Added message servers + Server dei messaggi aggiunti + No comment provided by engineer. + Additional accent Principale aggiuntivo @@ -660,6 +701,16 @@ Il cambio di indirizzo verrà interrotto. Verrà usato il vecchio indirizzo di ricezione. No comment provided by engineer. + + Address or 1-time link? + Indirizzo o link una tantum? + No comment provided by engineer. + + + Address settings + Impostazioni dell'indirizzo + No comment provided by engineer. + Admins can block a member for all. Gli amministratori possono bloccare un membro per tutti. @@ -680,6 +731,11 @@ Impostazioni avanzate No comment provided by engineer. + + All + Tutte + No comment provided by engineer. + All app data is deleted. Tutti i dati dell'app vengono eliminati. @@ -690,13 +746,18 @@ Tutte le chat e i messaggi verranno eliminati. Non è reversibile! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Tutte le chat verranno rimosse dall'elenco %@ e l'elenco eliminato. + alert message + All data is erased when it is entered. Tutti i dati vengono cancellati quando inserito. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Tutti i dati sono privati, nel tuo dispositivo. No comment provided by engineer. @@ -705,6 +766,11 @@ Tutti i membri del gruppo resteranno connessi. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Tutti i messaggi e i file vengono inviati **crittografati end-to-end**, con sicurezza resistenti alla quantistica nei messaggi diretti. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Tutti i messaggi verranno eliminati, non è reversibile! @@ -723,6 +789,16 @@ All profiles Tutti gli profili + profile dropdown + + + All reports will be archived for you. + Tutte le segnalazioni verranno archiviate per te. + No comment provided by engineer. + + + All servers + Tutti i server No comment provided by engineer. @@ -800,6 +876,11 @@ Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore) No comment provided by engineer. + + Allow to report messsages to moderators. + Consenti di segnalare messaggi ai moderatori. + No comment provided by engineer. + Allow to send SimpleX links. Consenti di inviare link di SimpleX. @@ -880,11 +961,21 @@ Viene creato un profilo di chat vuoto con il nome scelto e l'app si apre come al solito. No comment provided by engineer. + + Another reason + Altro motivo + report reason + 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: %@ @@ -900,6 +991,11 @@ L'app cripta i nuovi file locali (eccetto i video). No comment provided by engineer. + + App group: + Gruppo app: + No comment provided by engineer. + App icon Icona app @@ -915,6 +1011,11 @@ Il codice di accesso dell'app viene sostituito da un codice di autodistruzione. No comment provided by engineer. + + App session + Sessione dell'app + No comment provided by engineer. + App version Versione dell'app @@ -940,6 +1041,21 @@ Applica a No comment provided by engineer. + + Archive + Archivia + No comment provided by engineer. + + + Archive %lld reports? + Archiviare %lld segnalazioni? + No comment provided by engineer. + + + Archive all reports? + Archiviare tutte le segnalazioni? + No comment provided by engineer. + Archive and upload Archivia e carica @@ -950,6 +1066,21 @@ Archivia contatti per chattare più tardi. No comment provided by engineer. + + Archive report + Archivia la segnalazione + No comment provided by engineer. + + + Archive report? + Archiviare la segnalazione? + No comment provided by engineer. + + + Archive reports + Archivia segnalazioni + swipe action + Archived contacts Contatti archiviati @@ -1012,14 +1143,19 @@ Auto-accept contact requests - Auto-accetta richieste di contatto + Auto-accetta le richieste di contatto No comment provided by engineer. Auto-accept images - Auto-accetta immagini + Auto-accetta le immagini No comment provided by engineer. + + Auto-accept settings + Accetta automaticamente le impostazioni + alert title + Back Indietro @@ -1045,11 +1181,26 @@ Hash del messaggio errato No comment provided by engineer. + + Better calls + Chiamate migliorate + No comment provided by engineer. + Better groups Gruppi migliorati No comment provided by engineer. + + Better groups performance + Prestazioni dei gruppi migliorate + No comment provided by engineer. + + + Better message dates. + Date dei messaggi migliorate. + No comment provided by engineer. + Better messages Messaggi migliorati @@ -1060,6 +1211,26 @@ Rete migliorata No comment provided by engineer. + + Better notifications + Notifiche migliorate + No comment provided by engineer. + + + Better privacy and security + Privacy e sicurezza migliori + No comment provided by engineer. + + + Better security ✅ + Sicurezza migliorata ✅ + No comment provided by engineer. + + + Better user experience + Esperienza utente migliorata + No comment provided by engineer. + Black Nero @@ -1107,7 +1278,7 @@ Blur media - Sfocatura file multimediali + Sfocatura dei file multimediali No comment provided by engineer. @@ -1140,11 +1311,35 @@ Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Indirizzo di lavoro + No comment provided by engineer. + + + Business chats + Chat di lavoro + No comment provided by engineer. + + + Businesses + Lavorative + 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: +- 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! @@ -1193,7 +1388,8 @@ Cancel Annulla - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1409,7 @@ Cannot receive file Impossibile ricevere il file - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1426,16 @@ Cambia No comment provided by engineer. + + Change automatic message deletion? + Cambiare l'eliminazione automatica dei messaggi? + alert title + + + Change chat profiles + Modifica profili utente + authentication reason + Change database passphrase? Cambiare password del database? @@ -1274,11 +1480,21 @@ Change self-destruct passcode Cambia codice di autodistruzione authentication reason - set passcode view +set passcode view - - Chat archive - Archivio chat + + Chat + Chat + No comment provided by engineer. + + + Chat already exists + La chat esiste già + No comment provided by engineer. + + + Chat already exists! + La chat esiste già! No comment provided by engineer. @@ -1341,20 +1557,50 @@ Preferenze della chat No comment provided by engineer. + + Chat preferences were changed. + Le preferenze della chat sono state cambiate. + alert message + + + Chat profile + Profilo utente + No comment provided by engineer. + Chat theme Tema della chat No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + La chat verrà eliminata per tutti i membri, non è reversibile! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + La chat verrà eliminata solo per te, non è reversibile! + No comment provided by engineer. + Chats Chat 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. + No comment provided by engineer. + Check server address and try again. Controlla l'indirizzo del server e riprova. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1652,16 @@ Svuotare la conversazione? No comment provided by engineer. + + Clear group? + Svuotare il gruppo? + No comment provided by engineer. + + + Clear or delete group? + Svuotare o eliminare il gruppo? + No comment provided by engineer. + Clear private notes? Svuotare le note private? @@ -1426,6 +1682,11 @@ Modalità di colore No comment provided by engineer. + + Community guidelines violation + Violazione delle linee guida della comunità + report reason + Compare file Confronta file @@ -1441,14 +1702,49 @@ Completato No comment provided by engineer. + + Conditions accepted on: %@. + Condizioni accettate il: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Le condizioni sono state accettate per gli operatori: **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Le condizioni sono già state accettate per i seguenti operatori: **%@**. + No comment provided by engineer. + + + Conditions of use + Condizioni d'uso + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Le condizioni verranno accettate per gli operatori: **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Le condizioni verranno accettate il: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Le condizioni verranno accettate automaticamente per gli operatori attivi il: %@. + No comment provided by engineer. + Configure ICE servers Configura server ICE No comment provided by engineer. - - Configured %@ servers - Configurati %@ server + + Configure server operators + Configura gli operatori dei server No comment provided by engineer. @@ -1501,6 +1797,11 @@ Conferma caricamento No comment provided by engineer. + + Confirmed + Confermato + token status text + Connect Connetti @@ -1620,6 +1921,11 @@ Questo è il tuo link una tantum! Stato della connessione e dei server. No comment provided by engineer. + + Connection blocked + Connessione bloccata + No comment provided by engineer. + Connection error Errore di connessione @@ -1630,6 +1936,18 @@ Questo è il tuo link una tantum! Errore di connessione (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + La connessione è bloccata dall'operatore del server: +%@ + No comment provided by engineer. + + + Connection not ready. + Connessione non pronta. + No comment provided by engineer. + Connection notifications Notifiche di connessione @@ -1640,6 +1958,16 @@ Questo è il tuo link una tantum! Richiesta di connessione inviata! No comment provided by engineer. + + Connection requires encryption renegotiation. + La connessione richiede la rinegoziazione della crittografia. + No comment provided by engineer. + + + Connection security + Sicurezza della connessione + No comment provided by engineer. + Connection terminated Connessione terminata @@ -1715,6 +2043,11 @@ Questo è il tuo link una tantum! I contatti possono contrassegnare i messaggi per l'eliminazione; potrai vederli. No comment provided by engineer. + + Content violates conditions of use + Il contenuto viola le condizioni di utilizzo + blocking reason + Continue Continua @@ -1740,6 +2073,11 @@ Questo è il tuo link una tantum! Versione core: v%@ No comment provided by engineer. + + Corner + Angolo + No comment provided by engineer. + Correct name to %@? Correggere il nome a %@? @@ -1750,6 +2088,11 @@ Questo è il tuo link una tantum! Crea No comment provided by engineer. + + Create 1-time link + Crea link una tantum + No comment provided by engineer. + Create SimpleX address Crea indirizzo SimpleX @@ -1760,11 +2103,6 @@ Questo è il tuo link una tantum! Crea un gruppo usando un profilo casuale. No comment provided by engineer. - - Create an address to let people connect with you. - Crea un indirizzo per consentire alle persone di connettersi con te. - No comment provided by engineer. - Create file Crea file @@ -1785,6 +2123,11 @@ Questo è il tuo link una tantum! Crea link No comment provided by engineer. + + Create list + Crea elenco + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Crea un nuovo profilo nell'[app desktop](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2168,6 @@ Questo è il tuo link una tantum! Creato il: %@ copied message info - - Created on %@ - Creato il %@ - No comment provided by engineer. - Creating archive link Creazione link dell'archivio @@ -1845,6 +2183,11 @@ Questo è il tuo link una tantum! Codice di accesso attuale No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Il testo delle condizioni attuali testo non è stato caricato, puoi consultare le condizioni tramite questo link: + No comment provided by engineer. + Current passphrase… Password attuale… @@ -1865,6 +2208,11 @@ Questo è il tuo link una tantum! Tempo personalizzato No comment provided by engineer. + + Customizable message shape. + Forma dei messaggi personalizzabile. + No comment provided by engineer. + Customize theme Personalizza il tema @@ -1996,8 +2344,8 @@ Questo è il tuo link una tantum! Delete Elimina - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2382,14 @@ Questo è il tuo link una tantum! Elimina e avvisa il contatto No comment provided by engineer. - - Delete archive - Elimina archivio + + Delete chat + Elimina chat No comment provided by engineer. - - Delete chat archive? - Eliminare l'archivio della chat? + + Delete chat messages from your device. + Elimina i messaggi di chat dal tuo dispositivo. No comment provided by engineer. @@ -2054,6 +2402,11 @@ Questo è il tuo link una tantum! Eliminare il profilo di chat? No comment provided by engineer. + + Delete chat? + Eliminare la chat? + No comment provided by engineer. + Delete connection Elimina connessione @@ -2129,6 +2482,11 @@ Questo è il tuo link una tantum! Eliminare il link? No comment provided by engineer. + + Delete list? + Eliminare l'elenco? + alert title + Delete member message? Eliminare il messaggio del membro? @@ -2142,7 +2500,7 @@ Questo è il tuo link una tantum! Delete messages Elimina messaggi - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2517,11 @@ Questo è il tuo link una tantum! Eliminare il database vecchio? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Elimina o modera fino a 200 messaggi. + No comment provided by engineer. + Delete pending connection? Eliminare la connessione in attesa? @@ -2174,6 +2537,11 @@ Questo è il tuo link una tantum! Elimina coda server test step + + Delete report + Elimina la segnalazione + No comment provided by engineer. + Delete up to 20 messages at once. Elimina fino a 20 messaggi contemporaneamente. @@ -2209,6 +2577,11 @@ Questo è il tuo link una tantum! Errori di eliminazione No comment provided by engineer. + + Delivered even when Apple drops them. + Consegnati anche quando Apple li scarta. + No comment provided by engineer. + Delivery Consegna @@ -2309,8 +2682,13 @@ Questo è il tuo link una tantum! Messaggi diretti chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + I messaggi diretti tra i membri sono vietati in questa chat. + 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. @@ -2324,6 +2702,16 @@ Questo è il tuo link una tantum! Disattiva SimpleX Lock authentication reason + + Disable automatic message deletion? + Disattivare l'eliminazione automatica dei messaggi? + alert title + + + Disable delete messages + Disattiva eliminazione messaggi + alert button + Disable for all Disattiva per tutti @@ -2349,8 +2737,8 @@ Questo è il tuo link una tantum! I messaggi a tempo sono vietati in questa chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. I messaggi a tempo sono vietati in questo gruppo. No comment provided by engineer. @@ -2409,6 +2797,16 @@ Questo è il tuo link una tantum! Non inviare la cronologia ai nuovi membri. No comment provided by engineer. + + Do not use credentials with proxy. + Non usare credenziali con proxy. + No comment provided by engineer. + + + Documents: + Documenti: + No comment provided by engineer. + Don't create address Non creare un indirizzo @@ -2419,11 +2817,21 @@ Questo è il tuo link una tantum! Non attivare No comment provided by engineer. + + Don't miss important messages. + Non perdere messaggi importanti. + No comment provided by engineer. + Don't show again Non mostrare più No comment provided by engineer. + + Done + Fatto + No comment provided by engineer. + Downgrade and open chat Esegui downgrade e apri chat @@ -2432,7 +2840,8 @@ Questo è il tuo link una tantum! Download Scarica - chat item action + alert button +chat item action Download errors @@ -2449,6 +2858,11 @@ Questo è il tuo link una tantum! Scarica file server test step + + Download files + Scarica i file + alert action + Downloaded Scaricato @@ -2479,6 +2893,11 @@ Questo è il tuo link una tantum! Durata No comment provided by engineer. + + E2E encrypted notifications. + Notifiche crittografate E2E. + No comment provided by engineer. + Edit Modifica @@ -2499,6 +2918,11 @@ Questo è il tuo link una tantum! Attiva (mantieni sostituzioni) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + Attiva Flux nelle impostazioni "Rete e server" per una migliore privacy dei metadati. + No comment provided by engineer. + Enable SimpleX Lock Attiva SimpleX Lock @@ -2512,7 +2936,7 @@ Questo è il tuo link una tantum! Enable automatic message deletion? Attivare l'eliminazione automatica dei messaggi? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3063,11 @@ Questo è il tuo link una tantum! Rinegoziazione crittografia fallita. No comment provided by engineer. + + Encryption renegotiation in progress. + Rinegoziazione della crittografia in corso. + No comment provided by engineer. + Enter Passcode Inserisci il codice di accesso @@ -2704,26 +3133,36 @@ Questo è il tuo link una tantum! Errore nell'interruzione del cambio di indirizzo No comment provided by engineer. + + Error accepting conditions + Errore di accettazione delle condizioni + alert title + Error accepting contact request Errore nell'accettazione della richiesta di contatto No comment provided by engineer. - - Error accessing database file - Errore nell'accesso al file del database - No comment provided by engineer. - Error adding member(s) Errore di aggiunta membro/i No comment provided by engineer. + + Error adding server + Errore di aggiunta del server + alert title + Error changing address Errore nella modifica dell'indirizzo No comment provided by engineer. + + Error changing connection profile + Errore nel cambio di profilo di connessione + No comment provided by engineer. + Error changing role Errore nel cambio di ruolo @@ -2734,6 +3173,16 @@ Questo è il tuo link una tantum! Errore nella modifica dell'impostazione No comment provided by engineer. + + Error changing to incognito! + Errore nel passaggio a incognito! + No comment provided by engineer. + + + Error checking token status + Errore di controllo dello stato del token + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Errore di connessione al server di inoltro %@. Riprova più tardi. @@ -2754,6 +3203,11 @@ Questo è il tuo link una tantum! Errore nella creazione del link del gruppo No comment provided by engineer. + + Error creating list + Errore nella creazione dell'elenco + alert title + Error creating member contact Errore di creazione del contatto @@ -2769,6 +3223,11 @@ Questo è il tuo link una tantum! Errore nella creazione del profilo! No comment provided by engineer. + + Error creating report + Errore nella creazione del resoconto + No comment provided by engineer. + Error decrypting file Errore decifrando il file @@ -2849,9 +3308,14 @@ Questo è il tuo link una tantum! Errore di ingresso nel gruppo No comment provided by engineer. - - Error loading %@ servers - Errore nel caricamento dei server %@ + + Error loading servers + Errore nel caricamento dei server + alert title + + + Error migrating settings + Errore nella migrazione delle impostazioni No comment provided by engineer. @@ -2862,7 +3326,7 @@ Questo è il tuo link una tantum! Error receiving file Errore nella ricezione del file - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3338,36 @@ Questo è il tuo link una tantum! Errore di riconnessione ai server No comment provided by engineer. + + Error registering for notifications + Errore di registrazione per le notifiche + alert title + Error removing member Errore nella rimozione del membro No comment provided by engineer. + + Error reordering lists + Errore riordinando gli elenchi + alert title + Error resetting statistics Errore di azzeramento statistiche No comment provided by engineer. - - Error saving %@ servers - Errore nel salvataggio dei server %@ - No comment provided by engineer. - Error saving ICE servers Errore nel salvataggio dei server ICE No comment provided by engineer. + + Error saving chat list + Errore nel salvataggio dell'elenco di chat + alert title + Error saving group profile Errore nel salvataggio del profilo del gruppo @@ -2909,6 +3383,11 @@ Questo è il tuo link una tantum! Errore nel salvataggio della password nel portachiavi No comment provided by engineer. + + Error saving servers + Errore di salvataggio dei server + alert title + Error saving settings Errore di salvataggio delle impostazioni @@ -2954,16 +3433,26 @@ Questo è il tuo link una tantum! Errore nell'interruzione della chat No comment provided by engineer. + + Error switching profile + Errore nel cambio di profilo + No comment provided by engineer. + Error switching profile! Errore nel cambio di profilo! - No comment provided by engineer. + alertTitle Error synchronizing connection Errore nella sincronizzazione della connessione No comment provided by engineer. + + Error testing server connection + Errore provando la connessione al server + No comment provided by engineer. + Error updating group link Errore nell'aggiornamento del link del gruppo @@ -2974,6 +3463,11 @@ Questo è il tuo link una tantum! Errore nell'aggiornamento del messaggio No comment provided by engineer. + + Error updating server + Errore di aggiornamento del server + alert title + Error updating settings Errore nell'aggiornamento delle impostazioni @@ -3002,8 +3496,9 @@ Questo è il tuo link una tantum! Error: %@ Errore: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3515,11 @@ Questo è il tuo link una tantum! Errori No comment provided by engineer. + + Errors in servers configuration. + Errori nella configurazione dei server. + servers error + Even when disabled in the conversation. Anche quando disattivato nella conversazione. @@ -3035,6 +3535,11 @@ Questo è il tuo link una tantum! Espandi chat item action + + Expired + Scaduto + token status text + Export database Esporta database @@ -3075,20 +3580,49 @@ Questo è il tuo link una tantum! Veloce e senza aspettare che il mittente sia in linea! No comment provided by engineer. + + Faster deletion of groups. + Eliminazione dei gruppi più veloce. + No comment provided by engineer. + Faster joining and more reliable messages. Ingresso più veloce e messaggi più affidabili. No comment provided by engineer. + + Faster sending messages. + Invio dei messaggi più veloce. + No comment provided by engineer. + Favorite Preferito swipe action + + Favorites + Preferite + No comment provided by engineer. + File error Errore del file - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Errori di file: +%@ + alert message + + + File is blocked by server operator: +%@. + Il file è bloccato dall'operatore del server: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,8 +3679,8 @@ Questo è il tuo link una tantum! File e multimediali chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. File e contenuti multimediali sono vietati in questo gruppo. No comment provided by engineer. @@ -3215,21 +3749,71 @@ Questo è il tuo link una tantum! Correzione non supportata dal membro del gruppo No comment provided by engineer. + + For all moderators + Per tutti i moderatori + No comment provided by engineer. + + + For chat profile %@: + Per il profilo di chat %@: + servers error + For console Per console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Ad esempio, se il tuo contatto riceve messaggi tramite un server di SimpleX Chat, la tua app li consegnerà tramite un server Flux. + No comment provided by engineer. + + + For me + Per me + No comment provided by engineer. + + + For private routing + Per l'instradamento privato + No comment provided by engineer. + + + For social media + Per i social media + No comment provided by engineer. + Forward Inoltra chat item action + + Forward %d message(s)? + Inoltrare %d messaggio/i? + alert title + Forward and save messages Inoltra e salva i messaggi No comment provided by engineer. + + Forward messages + Inoltra i messaggi + alert action + + + Forward messages without files? + Inoltrare i messaggi senza file? + alert message + + + Forward up to 20 messages at once. + Inoltra fino a 20 messaggi alla volta. + No comment provided by engineer. + Forwarded Inoltrato @@ -3240,6 +3824,11 @@ Questo è il tuo link una tantum! Inoltrato da No comment provided by engineer. + + Forwarding %lld messages + Inoltro di %lld messaggi + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi. @@ -3289,11 +3878,6 @@ Errore: %2$@ Nome completo (facoltativo) No comment provided by engineer. - - Full name: - Nome completo: - No comment provided by engineer. - Fully decentralized – visible only to members. Completamente decentralizzato: visibile solo ai membri. @@ -3314,6 +3898,11 @@ Errore: %2$@ GIF e adesivi No comment provided by engineer. + + Get notified when mentioned. + Ricevi una notifica quando menzionato. + No comment provided by engineer. + Good afternoon! Buon pomeriggio! @@ -3379,41 +3968,6 @@ Errore: %2$@ Link del gruppo No comment provided by engineer. - - Group members can add message reactions. - I membri del gruppo possono aggiungere reazioni ai messaggi. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) - No comment provided by engineer. - - - Group members can send SimpleX links. - I membri del gruppo possono inviare link di Simplex. - No comment provided by engineer. - - - Group members can send direct messages. - I membri del gruppo possono inviare messaggi diretti. - No comment provided by engineer. - - - Group members can send disappearing messages. - I membri del gruppo possono inviare messaggi a tempo. - No comment provided by engineer. - - - Group members can send files and media. - I membri del gruppo possono inviare file e contenuti multimediali. - No comment provided by engineer. - - - Group members can send voice messages. - I membri del gruppo possono inviare messaggi vocali. - No comment provided by engineer. - Group message: Messaggio del gruppo: @@ -3454,11 +4008,21 @@ Errore: %2$@ Il gruppo verrà eliminato per te. Non è reversibile! No comment provided by engineer. + + Groups + Gruppi + No comment provided by engineer. + Help Aiuto No comment provided by engineer. + + Help admins moderating their groups. + Aiuta gli amministratori a moderare i loro gruppi. + No comment provided by engineer. + Hidden Nascosta @@ -3509,10 +4073,20 @@ Errore: %2$@ Come funziona SimpleX No comment provided by engineer. + + How it affects privacy + Come influisce sulla privacy + No comment provided by engineer. + + + How it helps privacy + Come aiuta la privacy + No comment provided by engineer. + How it works Come funziona - No comment provided by engineer. + alert button How to @@ -3539,6 +4113,11 @@ Errore: %2$@ Server ICE (uno per riga) No comment provided by engineer. + + IP address + Indirizzo IP + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Se non potete incontrarvi di persona, mostra il codice QR in una videochiamata o condividi il link. @@ -3579,8 +4158,8 @@ Errore: %2$@ Immediatamente No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immune a spam e abusi No comment provided by engineer. @@ -3614,6 +4193,13 @@ Errore: %2$@ Importazione archivio No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Consegna migliorata, utilizzo di traffico ridotto. +Altri miglioramenti sono in arrivo! + No comment provided by engineer. + Improved message delivery Consegna dei messaggi migliorata @@ -3644,6 +4230,16 @@ Errore: %2$@ Suoni nelle chiamate No comment provided by engineer. + + Inappropriate content + Contenuto inappropriato + report reason + + + Inappropriate profile + Profilo inappropriato + report reason + Incognito Incognito @@ -3714,6 +4310,11 @@ Errore: %2$@ Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Istantaneamente + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4322,6 @@ Errore: %2$@ No comment provided by engineer. - - Instantly - Istantaneamente - No comment provided by engineer. - Interface Interfaccia @@ -3736,6 +4332,31 @@ Errore: %2$@ Colori dell'interfaccia No comment provided by engineer. + + Invalid + Non valido + token status text + + + Invalid (bad token) + Non valido (token corrotto) + token status text + + + Invalid (expired) + Non valido (scaduto) + token status text + + + Invalid (unregistered) + Non valido (non registrato) + token status text + + + Invalid (wrong topic) + Non valido (argomento sbagliato) + token status text + Invalid QR code Codice QR non valido @@ -3774,7 +4395,7 @@ Errore: %2$@ Invalid server address! Indirizzo del server non valido! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4417,11 @@ Errore: %2$@ Invita membri No comment provided by engineer. + + Invite to chat + Invita in chat + No comment provided by engineer. + Invite to group Invita al gruppo @@ -3811,8 +4437,8 @@ Errore: %2$@ L'eliminazione irreversibile dei messaggi è vietata in questa chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. L'eliminazione irreversibile dei messaggi è vietata in questo gruppo. No comment provided by engineer. @@ -3902,7 +4528,7 @@ Questo è il tuo link per il gruppo %@! Keep Tieni - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4543,7 @@ Questo è il tuo link per il gruppo %@! Keep unused invitation? Tenere l'invito inutilizzato? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4580,16 @@ Questo è il tuo link per il gruppo %@! Esci swipe action + + Leave chat + Esci dalla chat + No comment provided by engineer. + + + Leave chat? + Uscire dalla chat? + No comment provided by engineer. + Leave group Esci dal gruppo @@ -3994,6 +4630,21 @@ Questo è il tuo link per il gruppo %@! Desktop collegati No comment provided by engineer. + + List + Elenco + swipe action + + + List name and emoji should be different for all lists. + Il nome dell'elenco e l'emoji dovrebbero essere diversi per tutte le liste. + No comment provided by engineer. + + + List name... + Nome elenco... + No comment provided by engineer. + Live message! Messaggio in diretta! @@ -4004,11 +4655,6 @@ Questo è il tuo link per il gruppo %@! Messaggi in diretta No comment provided by engineer. - - Local - Locale - No comment provided by engineer. - Local name Nome locale @@ -4029,11 +4675,6 @@ Questo è il tuo link per il gruppo %@! Modalità di blocco No comment provided by engineer. - - Make a private connection - Crea una connessione privata - No comment provided by engineer. - Make one message disappear Fai sparire un messaggio @@ -4044,21 +4685,11 @@ Questo è il tuo link per il gruppo %@! Rendi privato il profilo! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Assicurati che gli indirizzi dei server %@ siano nel formato corretto, uno per riga e non doppi (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Assicurati che gli indirizzi dei server WebRTC ICE siano nel formato corretto, uno per riga e non doppi. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Molte persone hanno chiesto: *se SimpleX non ha identificatori utente, come può recapitare i messaggi?* - No comment provided by engineer. - Mark deleted for everyone Contrassegna eliminato per tutti @@ -4104,6 +4735,16 @@ Questo è il tuo link per il gruppo %@! Membro inattivo item status text + + Member reports + Segnalazioni dei membri + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Il ruolo del membro verrà cambiato in "%@". Verranno notificati tutti i membri della chat. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Il ruolo del membro verrà cambiato in "%@". Tutti i membri del gruppo verranno avvisati. @@ -4114,11 +4755,61 @@ Questo è il tuo link per il gruppo %@! Il ruolo del membro verrà cambiato in "%@". Il membro riceverà un invito nuovo. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Il membro verrà rimosso dalla chat, non è reversibile! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Il membro verrà rimosso dal gruppo, non è reversibile! 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 irreversibly delete sent messages. (24 hours) + I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) + No comment provided by engineer. + + + Members can report messsages to moderators. + I membri possono segnalare messaggi ai moderatori. + No comment provided by engineer. + + + Members can send SimpleX links. + I membri del gruppo possono inviare link di Simplex. + No comment provided by engineer. + + + Members can send direct messages. + I membri del gruppo possono inviare messaggi diretti. + No comment provided by engineer. + + + Members can send disappearing messages. + I membri del gruppo possono inviare messaggi a tempo. + No comment provided by engineer. + + + Members can send files and media. + I membri del gruppo possono inviare file e contenuti multimediali. + No comment provided by engineer. + + + Members can send voice messages. + I membri del gruppo possono inviare messaggi vocali. + No comment provided by engineer. + + + Mention members 👋 + Menziona i membri 👋 + No comment provided by engineer. + Menus Menu @@ -4141,7 +4832,7 @@ Questo è il tuo link per il gruppo %@! Message draft - Bozza dei messaggi + Bozza del messaggio No comment provided by engineer. @@ -4169,8 +4860,8 @@ Questo è il tuo link per il gruppo %@! Le reazioni ai messaggi sono vietate in questa chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Le reazioni ai messaggi sono vietate in questo gruppo. No comment provided by engineer. @@ -4184,6 +4875,11 @@ Questo è il tuo link per il gruppo %@! Server dei messaggi No comment provided by engineer. + + Message shape + Forma del messaggio + No comment provided by engineer. + Message source remains private. La fonte del messaggio resta privata. @@ -4224,6 +4920,11 @@ Questo è il tuo link per il gruppo %@! I messaggi da %@ verranno mostrati! No comment provided by engineer. + + Messages in this chat will never be deleted. + I messaggi in questa chat non verranno mai eliminati. + alert message + Messages received Messaggi ricevuti @@ -4234,6 +4935,11 @@ Questo è il tuo link per il gruppo %@! Messaggi inviati No comment provided by engineer. + + Messages were deleted after you selected them. + I messaggi sono stati eliminati dopo che li hai selezionati. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione. @@ -4299,9 +5005,9 @@ Questo è il tuo link per il gruppo %@! La migrazione è completata No comment provided by engineer. - - Migrations: %@ - Migrazioni: %@ + + Migrations: + Migrazioni: No comment provided by engineer. @@ -4319,6 +5025,11 @@ Questo è il tuo link per il gruppo %@! Moderato il: %@ copied message info + + More + Altro + swipe action + More improvements are coming soon! Altri miglioramenti sono in arrivo! @@ -4329,6 +5040,11 @@ Questo è il tuo link per il gruppo %@! Connessione di rete più affidabile. No comment provided by engineer. + + More reliable notifications + Notifiche più affidabili + No comment provided by engineer. + Most likely this connection is deleted. Probabilmente questa connessione è stata eliminata. @@ -4342,7 +5058,12 @@ Questo è il tuo link per il gruppo %@! Mute Silenzia - swipe action + notification label action + + + Mute all + Silenzia tutto + notification label action Muted when inactive! @@ -4364,6 +5085,11 @@ Questo è il tuo link per il gruppo %@! Connessione di rete No comment provided by engineer. + + Network decentralization + Decentralizzazione della rete + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo. @@ -4374,6 +5100,11 @@ Questo è il tuo link per il gruppo %@! Gestione della rete No comment provided by engineer. + + Network operator + Operatore di rete + No comment provided by engineer. + Network settings Impostazioni di rete @@ -4384,11 +5115,26 @@ Questo è il tuo link per il gruppo %@! Stato della rete No comment provided by engineer. + + New + Nuovo + token status text + New Passcode Nuovo codice di accesso No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Le nuove credenziali SOCKS verranno usate ogni volta che avvii l'app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Le nuove credenziali SOCKS verranno usate per ogni server. + No comment provided by engineer. + New chat Nuova chat @@ -4409,11 +5155,6 @@ Questo è il tuo link per il gruppo %@! Nuovo contatto: notification - - New database archive - Nuovo archivio database - No comment provided by engineer. - New desktop app! Nuova app desktop! @@ -4424,6 +5165,11 @@ Questo è il tuo link per il gruppo %@! Nuovo nome da mostrare No comment provided by engineer. + + New events + Nuovi eventi + notification + New in %@ Novità nella %@ @@ -4449,6 +5195,11 @@ Questo è il tuo link per il gruppo %@! Nuova password… No comment provided by engineer. + + New server + Nuovo server + No comment provided by engineer. + No No @@ -4459,6 +5210,21 @@ Questo è il tuo link per il gruppo %@! Nessuna password dell'app Authentication unavailable + + No chats + Nessuna chat + No comment provided by engineer. + + + No chats found + Nessuna chat trovata + No comment provided by engineer. + + + No chats in list %@ + Nessuna chat nell'elenco %@ + No comment provided by engineer. + No contacts selected Nessun contatto selezionato @@ -4504,31 +5270,106 @@ Questo è il tuo link per il gruppo %@! Nessuna informazione, prova a ricaricare No comment provided by engineer. + + No media & file servers. + Nessun server di multimediali e file. + servers error + + + No message + Nessun messaggio + No comment provided by engineer. + + + No message servers. + Nessun server dei messaggi. + servers error + No network connection Nessuna connessione di rete No comment provided by engineer. + + No permission to record speech + Nessuna autorizzazione per registrare l'audio + No comment provided by engineer. + + + No permission to record video + Nessuna autorizzazione per registrare il video + No comment provided by engineer. + No permission to record voice message Nessuna autorizzazione per registrare messaggi vocali No comment provided by engineer. + + No push server + Locale + No comment provided by engineer. + No received or sent files Nessun file ricevuto o inviato No comment provided by engineer. + + No servers for private message routing. + Nessun server per l'instradamento dei messaggi privati. + servers error + + + No servers to receive files. + Nessun server per ricevere file. + servers error + + + No servers to receive messages. + Nessun server per ricevere messaggi. + servers error + + + No servers to send files. + Nessun server per inviare file. + servers error + + + No token! + Nessun token! + alert title + + + No unread chats + Nessuna chat non letta + No comment provided by engineer. + + + No user identifiers. + Nessun identificatore utente. + No comment provided by engineer. + Not compatible! Non compatibile! No comment provided by engineer. + + Notes + Note + No comment provided by engineer. + Nothing selected Nessuna selezione No comment provided by engineer. + + Nothing to forward! + Niente da inoltrare! + alert title + Notifications Notifiche @@ -4539,6 +5380,21 @@ Questo è il tuo link per il gruppo %@! Le notifiche sono disattivate! No comment provided by engineer. + + Notifications error + Errore delle notifiche + alert title + + + Notifications privacy + Privacy delle notifiche + No comment provided by engineer. + + + Notifications status + Stato delle notifiche + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5417,13 @@ Questo è il tuo link per il gruppo %@! Ok Ok - No comment provided by engineer. + alert button Old database Database vecchio No comment provided by engineer. - - Old database archive - Vecchio archivio del database - No comment provided by engineer. - One-time invitation link Link di invito una tantum @@ -4597,8 +5448,13 @@ Richiede l'attivazione della VPN. Gli host Onion non verranno usati. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Solo i proprietari della chat possono modificarne le preferenze. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**. No comment provided by engineer. @@ -4622,6 +5478,16 @@ Richiede l'attivazione della VPN. Solo i proprietari del gruppo possono attivare i messaggi vocali. No comment provided by engineer. + + Only sender and moderators see it + Solo il mittente e i moderatori lo vedono + No comment provided by engineer. + + + Only you and moderators see it + Solo tu e i moderatori lo vedete + No comment provided by engineer. + Only you can add message reactions. Solo tu puoi aggiungere reazioni ai messaggi. @@ -4675,13 +5541,18 @@ Richiede l'attivazione della VPN. Open Apri - No comment provided by engineer. + alert action Open Settings Apri le impostazioni No comment provided by engineer. + + Open changes + Apri le modifiche + No comment provided by engineer. + Open chat Apri chat @@ -4692,36 +5563,45 @@ Richiede l'attivazione della VPN. Apri la console della chat authentication reason + + Open conditions + Apri le condizioni + No comment provided by engineer. + Open group Apri gruppo No comment provided by engineer. + + Open link? + alert title + Open migration to another device Apri migrazione ad un altro dispositivo authentication reason - - Open server settings - Apri impostazioni server - No comment provided by engineer. - - - Open user profiles - Apri i profili utente - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protocollo e codice open source: chiunque può gestire i server. - No comment provided by engineer. - Opening app… Apertura dell'app… No comment provided by engineer. + + Operator + Operatore + No comment provided by engineer. + + + Operator server + Server dell'operatore + alert title + + + Or import archive file + O importa file archivio + No comment provided by engineer. + Or paste archive link O incolla il link dell'archivio @@ -4742,15 +5622,27 @@ Richiede l'attivazione della VPN. O mostra questo codice No comment provided by engineer. + + Or to share privately + O per condividere in modo privato + No comment provided by engineer. + + + Organize chats into lists + Organizza le chat in elenchi + No comment provided by engineer. + Other Altro No comment provided by engineer. - - Other %@ servers - Altri %@ server - No comment provided by engineer. + + Other file errors: +%@ + Altri errori di file: +%@ + alert message PING count @@ -4787,6 +5679,11 @@ Richiede l'attivazione della VPN. Codice di accesso impostato! No comment provided by engineer. + + Password + Password + No comment provided by engineer. + Password to show Password per mostrare @@ -4822,13 +5719,8 @@ Richiede l'attivazione della VPN. In attesa No comment provided by engineer. - - People can connect to you only via the links you share. - Le persone possono connettersi a te solo tramite i link che condividi. - No comment provided by engineer. - - - Periodically + + Periodic Periodicamente No comment provided by engineer. @@ -4931,11 +5823,31 @@ Errore: %@ Conserva la password in modo sicuro, NON potrai cambiarla se la perdi. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Prova a disattivare e riattivare le notifiche. + token info + + + Please wait for token activation to complete. + Attendi il completamento dell'attivazione del token. + token info + + + Please wait for token to be registered. + Attendi la registrazione del token. + token info + Polish interface Interfaccia polacca No comment provided by engineer. + + Port + Porta + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata @@ -4946,16 +5858,16 @@ Errore: %@ Conserva la bozza dell'ultimo messaggio, con gli allegati. No comment provided by engineer. - - Preset server - Server preimpostato - No comment provided by engineer. - Preset server address Indirizzo server preimpostato No comment provided by engineer. + + Preset servers + Server preimpostati + No comment provided by engineer. + Preview Anteprima @@ -4971,16 +5883,36 @@ Errore: %@ Privacy e sicurezza No comment provided by engineer. + + Privacy for your customers. + Privacy per i tuoi clienti. + No comment provided by engineer. + + + Privacy policy and conditions of use. + Informativa sulla privacy e condizioni d'uso. + No comment provided by engineer. + Privacy redefined Privacy ridefinita 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. + No comment provided by engineer. + Private filenames Nomi di file privati No comment provided by engineer. + + Private media file names. + Nomi privati dei file multimediali. + No comment provided by engineer. + Private message routing Instradamento privato dei messaggi @@ -5021,16 +5953,6 @@ Errore: %@ Immagini del profilo No comment provided by engineer. - - Profile name - Nome del profilo - No comment provided by engineer. - - - Profile name: - Nome del profilo: - No comment provided by engineer. - Profile password Password del profilo @@ -5044,7 +5966,7 @@ Errore: %@ Profile update will be sent to your contacts. L'aggiornamento del profilo verrà inviato ai tuoi contatti. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5988,11 @@ Errore: %@ Proibisci le reazioni ai messaggi. No comment provided by engineer. + + Prohibit reporting messages to moderators. + Vieta di segnalare messaggi ai moderatori. + No comment provided by engineer. + Prohibit sending SimpleX links. Vieta l'invio di link di SimpleX. @@ -5133,6 +6060,11 @@ Attivalo nelle impostazioni *Rete e server*. Server via proxy No comment provided by engineer. + + Proxy requires password + Il proxy richiede una password + No comment provided by engineer. + Push notifications Notifiche push @@ -5173,26 +6105,21 @@ Attivalo nelle impostazioni *Rete e server*. Leggi tutto No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Maggiori informazioni nel nostro repository GitHub. - 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). @@ -5323,11 +6250,26 @@ Attivalo nelle impostazioni *Rete e server*. Consumo di batteria ridotto No comment provided by engineer. + + Register + Registra + No comment provided by engineer. + + + Register notification token? + Registrare il token di notifica? + token info + + + Registered + Registrato + token status text + Reject Rifiuta reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6296,11 @@ Attivalo nelle impostazioni *Rete e server*. Rimuovi No comment provided by engineer. + + Remove archive? + Rimuovere l'archivio? + No comment provided by engineer. + Remove image Rimuovi immagine @@ -5419,6 +6366,56 @@ Attivalo nelle impostazioni *Rete e server*. Rispondi chat item action + + Report + Segnala + chat item action + + + Report content: only group moderators will see it. + Segnala contenuto: solo i moderatori del gruppo lo vedranno. + report reason + + + Report member profile: only group moderators will see it. + Segnala profilo: solo i moderatori del gruppo lo vedranno. + report reason + + + Report other: only group moderators will see it. + Segnala altro: solo i moderatori del gruppo lo vedranno. + report reason + + + Report reason? + Motivo della segnalazione? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Segnala spam: solo i moderatori del gruppo lo vedranno. + report reason + + + Report violation: only group moderators will see it. + Segnala violazione: solo i moderatori del gruppo lo vedranno. + report reason + + + Report: %@ + Segnalazione: %@ + report in notification + + + Reporting messages to moderators is prohibited. + È vietato segnalare messaggi ai moderatori. + No comment provided by engineer. + + + Reports + Segnalazioni + No comment provided by engineer. + Required Obbligatorio @@ -5504,6 +6501,11 @@ Attivalo nelle impostazioni *Rete e server*. Rivela chat item action + + Review conditions + Leggi le condizioni + No comment provided by engineer. + Revoke Revoca @@ -5534,6 +6536,11 @@ Attivalo nelle impostazioni *Rete e server*. Server SMP No comment provided by engineer. + + SOCKS proxy + Proxy SOCKS + No comment provided by engineer. + Safely receive files Ricevi i file in sicurezza @@ -5547,17 +6554,18 @@ Attivalo nelle impostazioni *Rete e server*. Save Salva - chat item action + alert button +chat item action Save (and notify contacts) Salva (e avvisa i contatti) - No comment provided by engineer. + alert button Save and notify contact Salva e avvisa il contatto - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6582,16 @@ Attivalo nelle impostazioni *Rete e server*. Salva e aggiorna il profilo del gruppo No comment provided by engineer. - - Save archive - Salva archivio - No comment provided by engineer. - - - Save auto-accept settings - Salva le impostazioni di accettazione automatica - No comment provided by engineer. - Save group profile Salva il profilo del gruppo No comment provided by engineer. + + Save list + Salva elenco + No comment provided by engineer. + Save passphrase and open chat Salva la password e apri la chat @@ -5602,7 +6605,7 @@ Attivalo nelle impostazioni *Rete e server*. Save preferences? Salvare le preferenze? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6620,18 @@ Attivalo nelle impostazioni *Rete e server*. Save servers? Salvare i server? - No comment provided by engineer. - - - Save settings? - Salvare le impostazioni? - No comment provided by engineer. + alert title Save welcome message? Salvare il messaggio di benvenuto? No comment provided by engineer. + + Save your profile? + Salvare il profilo? + alert title + Saved Salvato @@ -5649,6 +6652,11 @@ Attivalo nelle impostazioni *Rete e server*. Messaggio salvato message info title + + Saving %lld messages + Salvataggio di %lld messaggi + No comment provided by engineer. + Scale Scala @@ -5729,6 +6737,11 @@ Attivalo nelle impostazioni *Rete e server*. Seleziona chat item action + + Select chat profile + Seleziona il profilo di chat + No comment provided by engineer. + Selected %lld %lld selezionato @@ -5791,7 +6804,7 @@ Attivalo nelle impostazioni *Rete e server*. Send link previews - Invia anteprime dei link + Invia le anteprime dei link No comment provided by engineer. @@ -5819,9 +6832,9 @@ Attivalo nelle impostazioni *Rete e server*. Invia notifiche No comment provided by engineer. - - Send notifications: - Invia notifiche: + + Send private reports + Invia segnalazioni private No comment provided by engineer. @@ -5847,7 +6860,7 @@ Attivalo nelle impostazioni *Rete e server*. Sender cancelled file transfer. Il mittente ha annullato il trasferimento del file. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6957,16 @@ Attivalo nelle impostazioni *Rete e server*. Inviato via proxy No comment provided by engineer. + + Server + Server + No comment provided by engineer. + + + Server added to operator %@. + Server aggiunto all'operatore %@. + alert message + Server address Indirizzo server @@ -5959,6 +6982,21 @@ Attivalo nelle impostazioni *Rete e server*. L'indirizzo del server è incompatibile con le impostazioni di rete: %@. No comment provided by engineer. + + Server operator changed. + L'operatore del server è cambiato. + alert title + + + Server operators + Operatori server + No comment provided by engineer. + + + Server protocol changed. + Il protocollo del server è cambiato. + alert title + Server requires authorization to create queues, check password Il server richiede l'autorizzazione di creare code, controlla la password @@ -6014,6 +7052,11 @@ Attivalo nelle impostazioni *Rete e server*. Imposta 1 giorno No comment provided by engineer. + + Set chat name… + Imposta il nome della chat… + No comment provided by engineer. + Set contact name… Imposta nome del contatto… @@ -6034,6 +7077,11 @@ Attivalo nelle impostazioni *Rete e server*. Impostalo al posto dell'autenticazione di sistema. No comment provided by engineer. + + Set message expiration in chats. + Imposta la scadenza dei messaggi nelle chat. + No comment provided by engineer. + Set passcode Imposta codice @@ -6064,6 +7112,11 @@ Attivalo nelle impostazioni *Rete e server*. Impostazioni No comment provided by engineer. + + Settings were changed. + Le impostazioni sono state cambiate. + alert message + Shape profile images Forma delle immagini del profilo @@ -6072,22 +7125,38 @@ Attivalo nelle impostazioni *Rete e server*. Share Condividi - chat item action + alert action +chat item action Share 1-time link Condividi link una tantum No comment provided by engineer. + + Share 1-time link with a friend + Condividi link una tantum con un amico + No comment provided by engineer. + + + Share SimpleX address on social media. + Condividi l'indirizzo SimpleX sui social media. + No comment provided by engineer. + Share address Condividi indirizzo No comment provided by engineer. + + Share address publicly + Condividi indirizzo pubblicamente + No comment provided by engineer. + Share address with contacts? Condividere l'indirizzo con i contatti? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7168,11 @@ Attivalo nelle impostazioni *Rete e server*. Condividi link No comment provided by engineer. + + Share profile + Condividi il profilo + No comment provided by engineer. + Share this 1-time invite link Condividi questo link di invito una tantum @@ -6114,6 +7188,11 @@ Attivalo nelle impostazioni *Rete e server*. Condividi con i contatti No comment provided by engineer. + + Short link + Link breve + No comment provided by engineer. + Show QR code Mostra codice QR @@ -6169,6 +7248,11 @@ Attivalo nelle impostazioni *Rete e server*. Indirizzo SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat e Flux hanno concluso un accordo per includere server gestiti da Flux nell'app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. La sicurezza di SimpleX Chat è stata verificata da Trail of Bits. @@ -6199,6 +7283,21 @@ Attivalo nelle impostazioni *Rete e server*. Indirizzo SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + L'indirizzo SimpleX e i link una tantum sono sicuri da condividere tramite qualsiasi messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + Indirizzo SimpleX o link una tantum? + No comment provided by engineer. + + + SimpleX channel link + Link del canale SimpleX + simplex link type + SimpleX contact address Indirizzo di contatto SimpleX @@ -6219,8 +7318,8 @@ Attivalo nelle impostazioni *Rete e server*. Link di SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. I link di SimpleX sono vietati in questo gruppo. No comment provided by engineer. @@ -6234,6 +7333,11 @@ Attivalo nelle impostazioni *Rete e server*. Invito SimpleX una tantum simplex link type + + SimpleX protocols reviewed by Trail of Bits. + Protocolli di SimpleX esaminati da Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Modalità incognito semplificata @@ -6264,6 +7368,11 @@ Attivalo nelle impostazioni *Rete e server*. Leggera blur media + + Some app settings were not migrated. + Alcune impostazioni dell'app non sono state migrate. + No comment provided by engineer. + Some file(s) were not exported: Alcuni file non sono stati esportati: @@ -6279,11 +7388,24 @@ Attivalo nelle impostazioni *Rete e server*. Si sono verificati alcuni errori non fatali durante l'importazione: No comment provided by engineer. + + Some servers failed the test: +%@ + Alcuni server hanno fallito il test: +%@ + alert message + Somebody Qualcuno notification title + + Spam + Spam + blocking reason +report reason + Square, circle, or anything in between. Quadrata, circolare o qualsiasi forma tra le due. @@ -6329,11 +7451,6 @@ Attivalo nelle impostazioni *Rete e server*. Ferma la chat No comment provided by engineer. - - Stop chat to enable database actions - Ferma la chat per attivare le azioni del database - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Ferma la chat per esportare, importare o eliminare il database della chat. Non potrai ricevere e inviare messaggi mentre la chat è ferma. @@ -6362,18 +7479,23 @@ Attivalo nelle impostazioni *Rete e server*. Stop sharing Smetti di condividere - No comment provided by engineer. + alert action Stop sharing address? Smettere di condividere l'indirizzo? - No comment provided by engineer. + alert title Stopping chat Arresto della chat No comment provided by engineer. + + Storage + Archiviazione + No comment provided by engineer. + Strong Forte @@ -6404,6 +7526,16 @@ Attivalo nelle impostazioni *Rete e server*. Supporta SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Cambia tra audio e video durante la chiamata. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Cambia profilo di chat per inviti una tantum. + No comment provided by engineer. + System Sistema @@ -6424,6 +7556,11 @@ Attivalo nelle impostazioni *Rete e server*. Scadenza connessione TCP No comment provided by engineer. + + TCP port for messaging + Porta TCP per i messaggi + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7576,21 @@ Attivalo nelle impostazioni *Rete e server*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Coda + No comment provided by engineer. + Take picture Scatta foto 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 button Tocca il pulsante @@ -6482,13 +7629,18 @@ Attivalo nelle impostazioni *Rete e server*. Temporary file error Errore del file temporaneo - No comment provided by engineer. + file error alert title Test failed at step %@. Test fallito al passo %@. server test failure + + Test notifications + Prova le notifiche + No comment provided by engineer. + Test server Prova server @@ -6502,7 +7654,7 @@ Attivalo nelle impostazioni *Rete e server*. Tests failed! Test falliti! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7671,6 @@ Attivalo nelle impostazioni *Rete e server*. Grazie agli utenti – contribuite via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - La prima piattaforma senza alcun identificatore utente – privata by design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7683,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare. 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 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). @@ -6551,6 +7703,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, your contact may be offline. + La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline. + No comment provided by engineer. + The connection you accepted will be cancelled! La connessione che hai accettato verrà annullata! @@ -6571,6 +7728,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 + No comment provided by engineer. + The hash of the previous message is different. L'hash del messaggio precedente è diverso. @@ -6596,19 +7758,19 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.I messaggi verranno contrassegnati come moderati per tutti i membri. No comment provided by engineer. - - The next generation of private messaging - La nuova generazione di messaggistica privata - 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 profile is only shared with your contacts. - Il profilo è condiviso solo con i tuoi contatti. + + The same conditions will apply to operator **%@**. + Le stesse condizioni si applicheranno all'operatore **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + Il secondo operatore preimpostato nell'app! No comment provided by engineer. @@ -6626,16 +7788,31 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.I server per le nuove connessioni del profilo di chat attuale **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + I server per nuovi file del tuo profilo di chat attuale **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Il testo che hai incollato non è un link SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + L'archivio del database caricato verrà rimosso definitivamente dai server. + No comment provided by engineer. + Themes Temi No comment provided by engineer. + + These conditions will also apply for: **%@**. + Queste condizioni si applicheranno anche per: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Queste impostazioni sono per il tuo profilo attuale **%@**. @@ -6656,6 +7833,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Questa azione non può essere annullata: i messaggi inviati e ricevuti prima di quanto selezionato verranno eliminati. Potrebbe richiedere diversi minuti. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Questa azione non è reversibile: i messaggi inviati e ricevuti in questa chat prima della selezione verranno eliminati. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Questa azione non può essere annullata: il tuo profilo, i contatti, i messaggi e i file andranno persi in modo irreversibile. @@ -6701,11 +7883,21 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Questo è il tuo link una tantum! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Questo link è stato usato con un altro dispositivo mobile, creane uno nuovo sul desktop. No comment provided by engineer. + + This message was deleted or not received yet. + Questo messaggio è stato eliminato o non ancora ricevuto. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Questa impostazione si applica ai messaggi del profilo di chat attuale **%@**. @@ -6736,9 +7928,9 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Per creare una nuova connessione No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti. + + 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. No comment provided by engineer. @@ -6758,6 +7950,26 @@ You will be prompted to complete authentication before this feature is enabled.< Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzionalità. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti. + No comment provided by engineer. + + + To receive + Per ricevere + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Per registrare l'audio, concedi l'autorizzazione di usare il microfono. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Per registrare il video, concedi l'autorizzazione di usare la fotocamera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Per registrare un messaggio vocale, concedi l'autorizzazione all'uso del microfono. @@ -6768,11 +7980,21 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Per rivelare il tuo profilo nascosto, inserisci una password completa in un campo di ricerca nella pagina **I tuoi profili di chat**. No comment provided by engineer. + + To send + Per inviare + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Per supportare le notifiche push istantanee, il database della chat deve essere migrato. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + Per usare i server di **%@**, accetta le condizioni d'uso. + 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. @@ -6788,6 +8010,11 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Attiva/disattiva l'incognito quando ti colleghi. No comment provided by engineer. + + Token status: %@. + Stato del token: %@. + token status + Toolbar opacity Opacità barra degli strumenti @@ -6863,6 +8090,11 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Sbloccare il membro? No comment provided by engineer. + + Undelivered messages + Messaggi non consegnati + No comment provided by engineer. + Unexpected migration state Stato di migrazione imprevisto @@ -6911,7 +8143,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Unknown servers! Server sconosciuti! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +8180,18 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Unmute Riattiva notifiche - swipe action + notification label action Unread Non letto swipe action + + 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. @@ -6980,6 +8217,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Aggiornare le impostazioni? No comment provided by engineer. + + Updated conditions + Condizioni aggiornate + No comment provided by engineer. + Updating settings will re-connect the client to all servers. L'aggiornamento delle impostazioni riconnetterà il client a tutti i server. @@ -7020,16 +8262,36 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Invio dell'archivio No comment provided by engineer. + + Use %@ + Usa %@ + No comment provided by engineer. + Use .onion hosts Usa gli host .onion No comment provided by engineer. + + Use SOCKS proxy + Usa proxy SOCKS + No comment provided by engineer. + Use SimpleX Chat servers? Usare i server di SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + Usa la porta TCP %@ quando non è specificata alcuna porta. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + Usa la porta TCP 443 solo per i server preimpostati. + No comment provided by engineer. + Use chat Usa la chat @@ -7040,6 +8302,16 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa il profilo attuale No comment provided by engineer. + + Use for files + Usa per i file + No comment provided by engineer. + + + Use for messages + Usa per i messaggi + No comment provided by engineer. + Use for new connections Usa per connessioni nuove @@ -7080,6 +8352,16 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa il server No comment provided by engineer. + + Use servers + Usa i server + No comment provided by engineer. + + + Use short links (BETA) + Usa link brevi (BETA) + No comment provided by engineer. + Use the app while in the call. Usa l'app mentre sei in chiamata. @@ -7090,9 +8372,9 @@ 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. - - User profile - Profilo utente + + Use web port + Usa porta web No comment provided by engineer. @@ -7100,6 +8382,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Selezione utente No comment provided by engineer. + + Username + Nome utente + No comment provided by engineer. + Using SimpleX Chat servers. Utilizzo dei server SimpleX Chat. @@ -7170,11 +8457,21 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Video e file fino a 1 GB No comment provided by engineer. + + View conditions + Vedi le condizioni + No comment provided by engineer. + View security code Vedi codice di sicurezza No comment provided by engineer. + + View updated conditions + Vedi le condizioni aggiornate + No comment provided by engineer. + Visible history Cronologia visibile @@ -7190,8 +8487,8 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e I messaggi vocali sono vietati in questa chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. I messaggi vocali sono vietati in questo gruppo. No comment provided by engineer. @@ -7285,9 +8582,9 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Quando si connettono le chiamate audio e video. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Quando le persone chiedono di connettersi, puoi accettare o rifiutare. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Quando più di un operatore è attivato, nessuno di essi ha metadati per scoprire chi comunica con chi. No comment provided by engineer. @@ -7333,7 +8630,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Senza Tor o VPN, il tuo indirizzo IP sarà visibile a questi relay XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8657,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Server XFTP No comment provided by engineer. - - You - Tu - No comment provided by engineer. - You **must not** use the same database on two devices. **Non devi** usare lo stesso database su due dispositivi. @@ -7390,6 +8682,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Sei già connesso/a a %@. No comment provided by engineer. + + You are already connected with %@. + Sei già connesso/a con %@. + No comment provided by engineer. + You are already connecting to %@. Ti stai già connettendo a %@. @@ -7452,6 +8749,11 @@ Ripetere la richiesta di ingresso? Puoi cambiarlo nelle impostazioni dell'aspetto. No comment provided by engineer. + + You can configure servers via settings. + Puoi configurare i server nelle impostazioni. + No comment provided by engineer. + You can create it later Puoi crearlo più tardi @@ -7492,6 +8794,11 @@ Ripetere la richiesta di ingresso? Puoi inviare messaggi a %@ dai contatti archiviati. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + Puoi impostare il nome della connessione per ricordare con chi è stato condiviso il link. + 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. @@ -7507,11 +8814,6 @@ Ripetere la richiesta di ingresso? Puoi condividere questo indirizzo con i tuoi contatti per consentire loro di connettersi con **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Puoi avviare la chat via Impostazioni / Database o riavviando l'app @@ -7535,23 +8837,23 @@ Ripetere la richiesta di ingresso? You can view invitation link again in connection details. Puoi vedere di nuovo il link di invito nei dettagli di connessione. - No comment provided by engineer. + alert message You can't send messages! Non puoi inviare messaggi! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Tu decidi attraverso quale/i server **ricevere** i messaggi, i tuoi contatti quali server usi per inviare loro i messaggi. - No comment provided by engineer. - You could not be verified; please try again. 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 via this address! Hai già richiesto la connessione tramite questo indirizzo! @@ -7619,6 +8921,11 @@ Ripetere la richiesta di connessione? Hai inviato un invito al gruppo No comment provided by engineer. + + You should receive notifications. + Dovresti ricevere le notifiche. + token info + You will be connected to group when the group host's device is online, please wait or check later! Verrai connesso/a al gruppo quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi! @@ -7654,6 +8961,11 @@ Ripetere la richiesta di connessione? Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi. 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. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Non riceverai più messaggi da questo gruppo. La cronologia della chat verrà conservata. @@ -7674,31 +8986,16 @@ Ripetere la richiesta di connessione? Stai usando un profilo in incognito per questo gruppo: per impedire la condivisione del tuo profilo principale non è consentito invitare contatti No comment provided by engineer. - - Your %@ servers - I tuoi server %@ - No comment provided by engineer. - Your ICE servers I tuoi server ICE No comment provided by engineer. - - Your SMP servers - I tuoi server SMP - No comment provided by engineer. - Your SimpleX address Il tuo indirizzo SimpleX No comment provided by engineer. - - Your XFTP servers - I tuoi server XFTP - No comment provided by engineer. - Your calls Le tue chiamate @@ -7714,11 +9011,21 @@ Ripetere la richiesta di connessione? Il tuo database della chat non è crittografato: imposta la password per crittografarlo. No comment provided by engineer. + + Your chat preferences + Le tue preferenze della chat + alert title + Your chat profiles I tuoi profili di chat No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + La tua connessione è stata spostata a %@, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@). @@ -7734,6 +9041,11 @@ Ripetere la richiesta di connessione? I tuoi contatti resteranno connessi. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Le credenziali potrebbero essere inviate in chiaro. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Il tuo attuale database della chat verrà ELIMINATO e SOSTITUITO con quello importato. @@ -7764,33 +9076,36 @@ Ripetere la richiesta di connessione? Verrà condiviso il tuo profilo **%@**. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. -I server di SimpleX non possono vedere il tuo profilo. + + Your profile is stored on your device and only shared with your contacts. + Il profilo è condiviso solo con i tuoi contatti. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo. No comment provided by engineer. + + 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. + alert message + Your random profile Il tuo profilo casuale No comment provided by engineer. - - Your server - Il tuo server - No comment provided by engineer. - Your server address L'indirizzo del tuo server No comment provided by engineer. + + Your servers + I tuoi server + No comment provided by engineer. + Your settings Le tue impostazioni @@ -7831,6 +9146,11 @@ I server di SimpleX non possono vedere il tuo profilo. chiamata accettata call status + + accepted invitation + invito accettato + chat list item title + admin amministratore @@ -7866,6 +9186,11 @@ I server di SimpleX non possono vedere il tuo profilo. e altri %lld eventi No comment provided by engineer. + + archived report + segnalazione archiviata + No comment provided by engineer. + attempts tentativi @@ -7904,7 +9229,8 @@ I server di SimpleX non possono vedere il tuo profilo. blocked by admin bloccato dall'amministratore - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7948,7 +9274,7 @@ I server di SimpleX non possono vedere il tuo profilo. changed your role to %@ - cambiato il tuo ruolo in %@ + ha cambiato il tuo ruolo in %@ rcv group event chat item @@ -8019,7 +9345,7 @@ I server di SimpleX non possono vedere il tuo profilo. connecting… in connessione… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9400,8 @@ I server di SimpleX non possono vedere il tuo profilo. default (%@) predefinito (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9528,6 @@ I server di SimpleX non possono vedere il tuo profilo. errore No comment provided by engineer. - - event happened - evento accaduto - No comment provided by engineer. - expired scaduto @@ -8338,7 +9660,7 @@ I server di SimpleX non possono vedere il tuo profilo. member %1$@ changed to %2$@ - membro %1$@ cambiato in %2$@ + il membro %1$@ è diventato %2$@ profile update event chat item @@ -8376,20 +9698,20 @@ I server di SimpleX non possono vedere il tuo profilo. moderato da %@ marked deleted chat item preview text + + moderator + moderatore + member role + months mesi time unit - - mute - silenzia - No comment provided by engineer. - never mai - No comment provided by engineer. + delete after time new message @@ -8420,8 +9742,8 @@ I server di SimpleX non possono vedere il tuo profilo. off off enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9785,16 @@ I server di SimpleX non possono vedere il tuo profilo. peer-to-peer No comment provided by engineer. + + pending + in attesa + No comment provided by engineer. + + + pending approval + in attesa di approvazione + No comment provided by engineer. + quantum resistant e2e encryption crittografia e2e resistente alla quantistica @@ -8478,6 +9810,11 @@ I server di SimpleX non possono vedere il tuo profilo. conferma ricevuta… No comment provided by engineer. + + rejected + rifiutato + No comment provided by engineer. + rejected call chiamata rifiutata @@ -8508,6 +9845,11 @@ I server di SimpleX non possono vedere il tuo profilo. ti ha rimosso/a rcv group event chat item + + requested to connect + richiesto di connettersi + chat list item title + saved salvato @@ -8564,7 +9906,7 @@ ultimo msg ricevuto: %2$@ set new profile picture - impostata nuova immagine del profilo + ha impostato una nuova immagine del profilo profile update event chat item @@ -8607,11 +9949,6 @@ ultimo msg ricevuto: %2$@ stato sconosciuto No comment provided by engineer. - - unmute - riattiva notifiche - No comment provided by engineer. - unprotected non protetto @@ -8776,7 +10113,7 @@ ultimo msg ricevuto: %2$@
- +
@@ -8813,7 +10150,7 @@ ultimo msg ricevuto: %2$@
- +
@@ -8833,9 +10170,41 @@ ultimo msg ricevuto: %2$@
+ +
+ +
+ + + %d new events + %d nuovi eventi + notification body + + + From %d chat(s) + Da %d chat + notification body + + + From: %@ + Da: %@ + notification body + + + New events + Nuovi eventi + notification + + + New messages + Nuovi messaggi + notification + + +
- +
@@ -8857,7 +10226,7 @@ ultimo msg ricevuto: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/it.xcloc/contents.json b/apps/ios/SimpleX Localizations/it.xcloc/contents.json index 13870ab8dd..a42f254bd9 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/it.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "it", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 a545f3ba05..27134216a7 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (コピー可能) @@ -74,7 +47,7 @@ %@ (current) - %@ (現在) + %@ (現在) No comment provided by engineer. @@ -127,6 +100,16 @@ %@ は検証されています No comment provided by engineer. + + %@ server + %@ サーバー + No comment provided by engineer. + + + %@ servers + %@ サーバー + No comment provided by engineer. + %@ uploaded %@ アップロード済 @@ -137,6 +120,11 @@ %@ が接続を希望しています! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@や%@など%lld人のメンバー @@ -157,11 +145,36 @@ %d 日 time interval + + %d file(s) are still being downloaded. + %d 個のファイルをダウンロードしています。 + forward confirmation reason + + + %d file(s) failed to download. + %d 個のファイルがダウンロードに失敗しました。 + forward confirmation reason + + + %d file(s) were deleted. + %d 個のファイルが削除されました。 + forward confirmation reason + + + %d file(s) were not downloaded. + %d 個のファイルがダウンロードされていません。 + forward confirmation reason + %d hours %d 時 time interval + + %d messages not forwarded + %d 個のメッセージが未転送 + alert title + %d min %d 分 @@ -177,6 +190,10 @@ %d 秒 time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d 件のスキップされたメッセージ @@ -189,7 +206,7 @@ %lld - %lld + No comment provided by engineer. @@ -247,11 +264,6 @@ %lldつの新しいインターフェース言語 No comment provided by engineer. - - %lld second(s) - %lld 秒 - No comment provided by engineer. - %lld seconds %lld 秒 @@ -302,11 +314,6 @@ %u 件のメッセージがスキップされました。 No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (新規) @@ -317,33 +324,23 @@ (このデバイス v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。 No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **新しい連絡先を追加**: 連絡先のワンタイム QR コードまたはリンクを作成します。 - No comment provided by engineer. - **Create group**: to create a new group. **グループ作成**: 新しいグループを作成する。 No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **よりプライベート**: 20 分ごとに新しいメッセージを確認します。 デバイス トークンは SimpleX Chat サーバーと共有されますが、連絡先やメッセージの数は共有されません。 No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **最もプライベート**: SimpleX Chat 通知サーバーを使用せず、バックグラウンドで定期的にメッセージをチェックします (アプリの使用頻度によって異なります)。 No comment provided by engineer. @@ -357,11 +354,16 @@ **注意**: パスフレーズを紛失すると、パスフレーズを復元または変更できなくなります。 No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **推奨**: デバイス トークンと通知は SimpleX Chat 通知サーバーに送信されますが、メッセージの内容、サイズ、送信者は送信されません。 No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + **QRスキャン / リンクの貼り付け**: 受け取ったリンクで接続する。 + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **警告**: 即時の プッシュ通知には、キーチェーンに保存されたパスフレーズが必要です。 @@ -387,15 +389,13 @@ \*太字* No comment provided by engineer. - - , - , - 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)! - 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) に接続 (ベータ)! +- 配信証明を送信する (最大 20 人まで)。 +- より速く、より安定。 No comment provided by engineer. @@ -411,6 +411,9 @@ - optionally notify deleted contacts. - profile names with spaces. - and more! + - 任意で削除された連絡先へ通知します。 +- プロフィール名に空白を含めることができます。 +- and more! No comment provided by engineer. @@ -422,11 +425,6 @@ - 編集履歴。 No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 秒 @@ -440,7 +438,8 @@ 1 day 1日 - time interval + delete after time +time interval 1 hour @@ -455,12 +454,28 @@ 1 month 1ヶ月 - time interval + delete after time +time interval 1 week 1週間 - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + 使い捨てリンク + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + 使い捨てリンクは、*ひとつの連絡先にのみ* 使用できます - 対面または任意のチャットで共有してください。 + No comment provided by engineer. 5 minutes @@ -477,11 +492,6 @@ 30秒 No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -531,19 +541,13 @@ アドレス変更を中止しますか? No comment provided by engineer. - - About SimpleX - SimpleXについて - No comment provided by engineer. - About SimpleX Chat SimpleX Chat について No comment provided by engineer. - - About SimpleX address - SimpleXアドレスについて + + About operators No comment provided by engineer. @@ -554,8 +558,12 @@ Accept 承諾 accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + No comment provided by engineer. Accept connection request? @@ -571,7 +579,11 @@ Accept incognito シークレットモードで承諾 accept contact request via notification - swipe action +swipe action + + + Accepted conditions + No comment provided by engineer. Acknowledged @@ -581,6 +593,10 @@ Acknowledgement errors No comment provided by engineer. + + Active + token status text + Active connections No comment provided by engineer. @@ -590,13 +606,12 @@ プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 No comment provided by engineer. - - Add contact + + Add friends No comment provided by engineer. - - Add preset servers - 既存サーバを追加 + + Add list No comment provided by engineer. @@ -614,16 +629,38 @@ QRコードでサーバを追加する。 No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device 別の端末に追加 No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message ウェルカムメッセージを追加 No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + + + Added media & file servers + 追加されたメディア & ファイルサーバー + No comment provided by engineer. + + + Added message servers + 追加されたメッセージサーバー + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -646,6 +683,14 @@ アドレス変更は中止されます。古い受信アドレスが使用されます。 No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -662,6 +707,11 @@ Advanced settings + 詳細設定 + No comment provided by engineer. + + + All No comment provided by engineer. @@ -674,13 +724,17 @@ 全チャットとメッセージが削除されます(※元に戻せません※)! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. 入力するとすべてのデータが消去されます。 No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. @@ -688,6 +742,10 @@ グループ全員の接続が継続します。 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! No comment provided by engineer. @@ -703,6 +761,15 @@ All profiles + すべてのプロフィール + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -745,7 +812,7 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) - 送信相手も永久メッセージ削除を許可する時のみに許可する。 + 送信相手も永久メッセージ削除を許可する時のみに許可する。(24時間) No comment provided by engineer. @@ -770,6 +837,7 @@ Allow sharing + 共有を許可 No comment provided by engineer. @@ -777,6 +845,10 @@ 送信済みメッセージの永久削除を許可する。(24時間) No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. SimpleXリンクの送信を許可。 @@ -844,6 +916,7 @@ Always use private routing. + プライベートルーティングを常に使用する。 No comment provided by engineer. @@ -856,11 +929,20 @@ 指定された名前の空のチャット プロファイルが作成され、アプリが通常どおり開きます。 No comment provided by engineer. + + Another reason + report reason + Answer call 通話に応答 No comment provided by engineer. + + Anybody can host servers. + プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 + No comment provided by engineer. + App build: %@ アプリのビルド: %@ @@ -876,6 +958,10 @@ アプリは新しいローカルファイル(ビデオを除く)を暗号化します。 No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon アプリのアイコン @@ -891,6 +977,10 @@ アプリのパスコードは自己破壊パスコードに置き換えられます。 No comment provided by engineer. + + App session + No comment provided by engineer. + App version アプリのバージョン @@ -903,25 +993,52 @@ Appearance - 見た目 + アピアランス No comment provided by engineer. Apply + 適用 No comment provided by engineer. Apply to + に適用する + No comment provided by engineer. + + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? No comment provided by engineer. Archive and upload + アーカイブとアップロード No comment provided by engineer. Archive contacts to chat later. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts No comment provided by engineer. @@ -990,6 +1107,10 @@ 画像を自動的に受信 No comment provided by engineer. + + Auto-accept settings + alert title + Back 戻る @@ -1013,10 +1134,22 @@ メッセージのハッシュ値問題 No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + No comment provided by engineer. + Better messages より良いメッセージ @@ -1026,6 +1159,22 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1096,11 +1245,29 @@ ブルガリア語、フィンランド語、タイ語、ウクライナ語 - ユーザーと [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)に感謝します! No comment provided by engineer. + + Business address + No comment provided by engineer. + + + Business chats + No comment provided by engineer. + + + Businesses + 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: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! 通話は既に終了してます! @@ -1144,7 +1311,8 @@ Cancel 中止 - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1162,7 +1330,7 @@ Cannot receive file ファイル受信ができません - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1177,6 +1345,14 @@ 変更 No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + authentication reason + Change database passphrase? データベースのパスフレーズを更新しますか? @@ -1221,11 +1397,18 @@ Change self-destruct passcode 自己破壊パスコードを変更する authentication reason - set passcode view +set passcode view - - Chat archive - チャットのアーカイブ + + Chat + No comment provided by engineer. + + + Chat already exists + No comment provided by engineer. + + + Chat already exists! No comment provided by engineer. @@ -1283,8 +1466,26 @@ チャット設定 No comment provided by engineer. + + Chat preferences were changed. + alert message + + + Chat profile + ユーザープロフィール + No comment provided by engineer. + Chat theme + チャットテーマ + No comment provided by engineer. + + + 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. @@ -1292,10 +1493,18 @@ チャット No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. サーバのアドレスを確認してから再度試してください。 - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1318,10 +1527,12 @@ Chunks deleted + チャンクが削除されました No comment provided by engineer. Chunks downloaded + チャンクがダウンロードされました No comment provided by engineer. @@ -1343,8 +1554,17 @@ ダイアログのクリアしますか? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? + プライベートノートを消しますか? No comment provided by engineer. @@ -1358,8 +1578,13 @@ Color mode + 色設定 No comment provided by engineer. + + Community guidelines violation + report reason + Compare file ファイルを比較 @@ -1372,6 +1597,35 @@ Completed + 完了 + No comment provided by engineer. + + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. No comment provided by engineer. @@ -1379,8 +1633,8 @@ ICEサーバを設定 No comment provided by engineer. - - Configured %@ servers + + Configure server operators No comment provided by engineer. @@ -1428,6 +1682,10 @@ Confirm upload No comment provided by engineer. + + Confirmed + token status text + Connect 接続 @@ -1444,10 +1702,12 @@ Connect to desktop + デスクトップに接続 No comment provided by engineer. Connect to your friends faster. + 友達ともっと速くつながりましょう。 No comment provided by engineer. @@ -1484,22 +1744,27 @@ This is your own one-time link! Connected + 接続中 No comment provided by engineer. Connected desktop + デスクトップに接続済 No comment provided by engineer. Connected servers + 接続中のサーバ No comment provided by engineer. Connected to desktop + デスクトップに接続済 No comment provided by engineer. Connecting + 接続待ち No comment provided by engineer. @@ -1514,10 +1779,12 @@ This is your own one-time link! Connecting to contact, please wait or check later! + 連絡先に接続中です。しばらくお待ちいただくか、後で確認してください! No comment provided by engineer. Connecting to desktop + デスクトップに接続中 No comment provided by engineer. @@ -1527,6 +1794,11 @@ This is your own one-time link! Connection and servers status. + 接続とサーバーのステータス。 + No comment provided by engineer. + + + Connection blocked No comment provided by engineer. @@ -1539,6 +1811,15 @@ This is your own one-time link! 接続エラー (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications No comment provided by engineer. @@ -1548,8 +1829,17 @@ This is your own one-time link! 接続リクエストを送信しました! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + No comment provided by engineer. + Connection terminated + 接続停止 No comment provided by engineer. @@ -1617,6 +1907,10 @@ This is your own one-time link! 連絡先はメッセージを削除対象とすることができます。あなたには閲覧可能です。 No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue 続ける @@ -1640,6 +1934,10 @@ This is your own one-time link! コアのバージョン: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -1649,6 +1947,10 @@ This is your own one-time link! 作成 No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address SimpleXアドレスの作成 @@ -1658,11 +1960,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - 人とつながるためのアドレスを作成する。 - No comment provided by engineer. - Create file ファイルを作成 @@ -1682,6 +1979,10 @@ This is your own one-time link! リンクを生成する No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 [デスクトップアプリ](https://simplex.chat/downloads/)で新しいプロファイルを作成します。 💻 @@ -1689,6 +1990,7 @@ This is your own one-time link! Create profile + プロフィールを作成する No comment provided by engineer. @@ -1718,11 +2020,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - %@ によって作成されました - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -1736,6 +2033,10 @@ This is your own one-time link! 現在のパスコード No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… 現在の暗証フレーズ… @@ -1755,8 +2056,13 @@ This is your own one-time link! カスタム時間 No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme + カスタムテーマ No comment provided by engineer. @@ -1766,6 +2072,7 @@ This is your own one-time link! Dark mode colors + ダークモードカラー No comment provided by engineer. @@ -1868,6 +2175,7 @@ This is your own one-time link! Debug delivery + 配信のデバッグ No comment provided by engineer. @@ -1883,8 +2191,8 @@ This is your own one-time link! Delete 削除 - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -1918,14 +2226,12 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - アーカイブを削除 + + Delete chat No comment provided by engineer. - - Delete chat archive? - チャットのアーカイブを削除しますか? + + Delete chat messages from your device. No comment provided by engineer. @@ -1938,6 +2244,10 @@ This is your own one-time link! チャットのプロフィールを削除しますか? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection 接続を削除する @@ -2011,6 +2321,10 @@ This is your own one-time link! リンクを削除しますか? No comment provided by engineer. + + Delete list? + alert title + Delete member message? メンバーのメッセージを削除しますか? @@ -2024,7 +2338,7 @@ This is your own one-time link! Delete messages メッセージを削除 - No comment provided by engineer. + alert button Delete messages after @@ -2041,6 +2355,10 @@ This is your own one-time link! 古いデータベースを削除しますか? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? 接続待ちの接続を削除しますか? @@ -2056,6 +2374,10 @@ This is your own one-time link! 待ち行列を削除 server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. No comment provided by engineer. @@ -2087,6 +2409,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery 配信 @@ -2117,6 +2443,7 @@ This is your own one-time link! Desktop devices + デスクトップ機器 No comment provided by engineer. @@ -2146,6 +2473,7 @@ This is your own one-time link! Developer options + 開発者向けの設定 No comment provided by engineer. @@ -2178,8 +2506,12 @@ This is your own one-time link! ダイレクトメッセージ chat feature - - Direct messages between members are prohibited in this group. + + 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. @@ -2193,6 +2525,14 @@ This is your own one-time link! SimpleXロックを無効にする authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all すべて無効 @@ -2217,8 +2557,8 @@ This is your own one-time link! このチャットでは消えるメッセージが使用禁止です。 No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. このグループでは消えるメッセージが使用禁止です。 No comment provided by engineer. @@ -2272,9 +2612,17 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address - アドレスを作成しないでください + アドレスを作成しない No comment provided by engineer. @@ -2282,11 +2630,19 @@ This is your own one-time link! 有効にしない No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again 次から表示しない No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat ダウングレードしてチャットを開く @@ -2294,7 +2650,8 @@ This is your own one-time link! Download - chat item action + alert button +chat item action Download errors @@ -2309,6 +2666,10 @@ This is your own one-time link! ファイルをダウンロード server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2335,6 +2696,10 @@ This is your own one-time link! 間隔 No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit 編集する @@ -2355,6 +2720,10 @@ This is your own one-time link! 有効にする(設定の優先を維持) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock SimpleXロックを有効にする @@ -2368,7 +2737,7 @@ This is your own one-time link! Enable automatic message deletion? 自動メッセージ削除を有効にしますか? - No comment provided by engineer. + alert title Enable camera access @@ -2488,6 +2857,10 @@ This is your own one-time link! Encryption re-negotiation failed. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode パスコードを入力 @@ -2549,26 +2922,33 @@ This is your own one-time link! アドレス変更中止エラー No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request 連絡先リクエストの承諾にエラー発生 No comment provided by engineer. - - Error accessing database file - データベースファイルへのアクセスエラー - No comment provided by engineer. - Error adding member(s) メンバー追加にエラー発生 No comment provided by engineer. + + Error adding server + alert title + Error changing address アドレス変更にエラー発生 No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role 役割変更にエラー発生 @@ -2579,6 +2959,14 @@ This is your own one-time link! 設定変更にエラー発生 No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2598,6 +2986,10 @@ This is your own one-time link! グループリンク生成にエラー発生 No comment provided by engineer. + + Error creating list + alert title + Error creating member contact メンバー連絡先の作成中にエラーが発生 @@ -2612,6 +3004,10 @@ This is your own one-time link! プロフィール作成にエラー発生! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file ファイルの復号エラー @@ -2689,9 +3085,12 @@ This is your own one-time link! グループ参加にエラー発生 No comment provided by engineer. - - Error loading %@ servers - %@ サーバーのロード中にエラーが発生 + + Error loading servers + alert title + + + Error migrating settings No comment provided by engineer. @@ -2701,7 +3100,7 @@ This is your own one-time link! Error receiving file ファイル受信にエラー発生 - No comment provided by engineer. + alert title Error reconnecting server @@ -2711,25 +3110,32 @@ This is your own one-time link! Error reconnecting servers No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member メンバー除名にエラー発生 No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - %@ サーバの保存エラー - No comment provided by engineer. - Error saving ICE servers ICEサーバ保存にエラー発生 No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile グループのプロフィール保存にエラー発生 @@ -2745,6 +3151,10 @@ This is your own one-time link! キーチェーンにパスフレーズを保存にエラー発生 No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2787,16 +3197,24 @@ This is your own one-time link! チャット停止にエラー発生 No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! プロフィール切り替えにエラー発生! - No comment provided by engineer. + alertTitle Error synchronizing connection 接続の同期エラー No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link グループのリンクのアップデートにエラー発生 @@ -2807,6 +3225,10 @@ This is your own one-time link! メッセージの更新にエラー発生 No comment provided by engineer. + + Error updating server + alert title + Error updating settings 設定の更新にエラー発生 @@ -2833,8 +3255,9 @@ This is your own one-time link! Error: %@ エラー : %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2850,6 +3273,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. 会話中に無効になっている場合でも。 @@ -2864,6 +3291,10 @@ This is your own one-time link! Expand chat item action + + Expired + token status text + Export database データベースをエキスポート @@ -2902,18 +3333,40 @@ This is your own one-time link! 送信者がオンラインになるまでの待ち時間がなく、速い! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite お気に入り swipe action + + Favorites + No comment provided by engineer. + File error - No comment provided by engineer. + file error alert title + + + File errors: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -2965,8 +3418,8 @@ This is your own one-time link! ファイルとメディア chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. このグループでは、ファイルとメディアは禁止されています。 No comment provided by engineer. @@ -3032,19 +3485,59 @@ This is your own one-time link! グループメンバーによる修正はサポートされていません No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + servers error + For console コンソール No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3053,6 +3546,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3094,11 +3591,6 @@ Error: %2$@ フルネーム (任意): No comment provided by engineer. - - Full name: - フルネーム: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -3118,6 +3610,10 @@ Error: %2$@ GIFとステッカー No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! message preview @@ -3179,40 +3675,6 @@ Error: %2$@ グループのリンク No comment provided by engineer. - - Group members can add message reactions. - グループメンバーはメッセージへのリアクションを追加できます。 - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - グループのメンバーがメッセージを完全削除することができます。(24時間) - No comment provided by engineer. - - - Group members can send SimpleX links. - No comment provided by engineer. - - - Group members can send direct messages. - グループのメンバーがダイレクトメッセージを送信できます。 - No comment provided by engineer. - - - Group members can send disappearing messages. - グループのメンバーが消えるメッセージを送信できます。 - No comment provided by engineer. - - - Group members can send files and media. - グループメンバーはファイルやメディアを送信できます。 - No comment provided by engineer. - - - Group members can send voice messages. - グループのメンバーが音声メッセージを送信できます。 - No comment provided by engineer. - Group message: グループメッセージ: @@ -3253,11 +3715,19 @@ Error: %2$@ あなたにとってグループが削除されます (※元に戻せません※)! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help ヘルプ No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden プライベート @@ -3307,10 +3777,17 @@ Error: %2$@ SimpleX の仕組み No comment provided by engineer. + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + No comment provided by engineer. + How it works - 技術の説明 - No comment provided by engineer. + alert button How to @@ -3336,6 +3813,10 @@ Error: %2$@ ICEサーバ (1行に1サーバ) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. 直接会えない場合は、ビデオ通話で QR コードを表示するか、リンクを共有してください。 @@ -3376,8 +3857,8 @@ Error: %2$@ 即座に No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam スパムや悪質送信を防止 No comment provided by engineer. @@ -3408,6 +3889,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -3435,6 +3921,14 @@ Error: %2$@ In-call sounds No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito シークレットモード @@ -3503,6 +3997,11 @@ Error: %2$@ インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + 即時 + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3510,11 +4009,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - すぐに - No comment provided by engineer. - Interface インターフェース @@ -3524,6 +4018,26 @@ Error: %2$@ Interface colors No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code No comment provided by engineer. @@ -3556,7 +4070,7 @@ Error: %2$@ Invalid server address! 無効なサーバアドレス! - No comment provided by engineer. + alert title Invalid status @@ -3578,6 +4092,10 @@ Error: %2$@ メンバーを招待する No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group グループに招待する @@ -3593,8 +4111,8 @@ Error: %2$@ このチャットではメッセージの完全削除が使用禁止です。 No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. このグループではメッセージの完全削除が使用禁止です。 No comment provided by engineer. @@ -3677,7 +4195,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3689,7 +4207,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3726,6 +4244,14 @@ This is your link for group %@! 脱退 swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group グループを脱退 @@ -3763,6 +4289,18 @@ This is your link for group %@! Linked desktops No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! ライブメッセージ! @@ -3773,11 +4311,6 @@ This is your link for group %@! ライブメッセージ No comment provided by engineer. - - Local - 自分のみ - No comment provided by engineer. - Local name ローカルネーム @@ -3798,11 +4331,6 @@ This is your link for group %@! ロックモード No comment provided by engineer. - - Make a private connection - プライベートな接続をする - No comment provided by engineer. - Make one message disappear メッセージを1つ消す @@ -3813,21 +4341,11 @@ This is your link for group %@! プロフィールを非表示にできます! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - %@ サーバー アドレスが正しい形式で、行が区切られており、重複していないことを確認してください (%@)。 - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. WebRTC ICEサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - 多くの人が次のような質問をしました: *SimpleX にユーザー識別子がない場合、どうやってメッセージを配信できるのですか?* - No comment provided by engineer. - Mark deleted for everyone 全員に対して削除済みマークを付ける @@ -3870,6 +4388,14 @@ This is your link for group %@! Member inactive item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. メンバーの役割が "%@" に変更されます。 グループメンバー全員に通知されます。 @@ -3880,11 +4406,57 @@ This is your link for group %@! メンバーの役割が "%@" に変更されます。 メンバーは新たな招待を受け取ります。 No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! メンバーをグループから除名する (※元に戻せません※)! No comment provided by engineer. + + Members can add message reactions. + グループメンバーはメッセージへのリアクションを追加できます。 + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + グループのメンバーがメッセージを完全削除することができます。(24時間) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + グループのメンバーがダイレクトメッセージを送信できます。 + No comment provided by engineer. + + + Members can send disappearing messages. + グループのメンバーが消えるメッセージを送信できます。 + No comment provided by engineer. + + + Members can send files and media. + グループメンバーはファイルやメディアを送信できます。 + No comment provided by engineer. + + + Members can send voice messages. + グループのメンバーが音声メッセージを送信できます。 + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus No comment provided by engineer. @@ -3929,8 +4501,8 @@ This is your link for group %@! このチャットではメッセージへのリアクションは禁止されています。 No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. このグループではメッセージへのリアクションは禁止されています。 No comment provided by engineer. @@ -3942,6 +4514,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3977,6 +4553,10 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received No comment provided by engineer. @@ -3985,12 +4565,18 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**エンドツーエンドの暗号化**によって保護されます。 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. @@ -3999,6 +4585,7 @@ This is your link for group %@! Migrate from another device + 別の端末から移行 No comment provided by engineer. @@ -4041,9 +4628,9 @@ This is your link for group %@! 移行が完了しました No comment provided by engineer. - - Migrations: %@ - 移行: %@ + + Migrations: + 移行: No comment provided by engineer. @@ -4061,6 +4648,10 @@ This is your link for group %@! モデレーターによって介入済み: %@ copied message info + + More + swipe action + More improvements are coming soon! まだまだ改善してまいります! @@ -4070,6 +4661,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. おそらく、この接続は削除されています。 @@ -4083,7 +4678,11 @@ This is your link for group %@! Mute ミュート - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4104,6 +4703,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4112,6 +4715,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings ネットワーク設定 @@ -4122,11 +4729,23 @@ This is your link for group %@! ネットワーク状況 No comment provided by engineer. + + New + token status text + New Passcode 新しいパスコード No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + No comment provided by engineer. + New chat No comment provided by engineer. @@ -4145,11 +4764,6 @@ This is your link for group %@! 新しい連絡先: notification - - New database archive - 新しいデータベースのアーカイブ - No comment provided by engineer. - New desktop app! 新しいデスクトップアプリ! @@ -4160,6 +4774,10 @@ This is your link for group %@! 新たな表示名 No comment provided by engineer. + + New events + notification + New in %@ %@ の新機能 @@ -4184,6 +4802,10 @@ This is your link for group %@! 新しいパスフレーズ… No comment provided by engineer. + + New server + No comment provided by engineer. + No いいえ @@ -4194,6 +4816,18 @@ This is your link for group %@! アプリのパスワードはありません Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected 連絡先が選択されてません @@ -4237,28 +4871,90 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection No comment provided by engineer. + + No permission to record speech + No comment provided by engineer. + + + No permission to record video + No comment provided by engineer. + No permission to record voice message 音声メッセージを録音する権限がありません No comment provided by engineer. + + No push server + 自分のみ + No comment provided by engineer. + No received or sent files 送受信済みのファイルがありません No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 + No comment provided by engineer. + Not compatible! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications 通知 @@ -4269,6 +4965,18 @@ This is your link for group %@! 通知が無効になっています! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4290,18 +4998,13 @@ This is your link for group %@! Ok OK - No comment provided by engineer. + alert button Old database 古いデータベース No comment provided by engineer. - - Old database archive - 過去のデータベースアーカイブ - No comment provided by engineer. - One-time invitation link 使い捨ての招待リンク @@ -4326,8 +5029,12 @@ VPN を有効にする必要があります。 オニオンのホストが使われません。 No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. **2 レイヤーのエンドツーエンド暗号化**を使用して送信されたユーザー プロファイル、連絡先、グループ、メッセージを保存できるのはクライアント デバイスのみです。 No comment provided by engineer. @@ -4350,6 +5057,14 @@ VPN を有効にする必要があります。 音声メッセージを利用可能に設定できるのはグループのオーナーだけです。 No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. メッセージへのリアクションを追加できるのは、あなただけです。 @@ -4403,13 +5118,17 @@ VPN を有効にする必要があります。 Open 開く - No comment provided by engineer. + alert action Open Settings 設定を開く No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat チャットを開く @@ -4420,32 +5139,38 @@ VPN を有効にする必要があります。 チャットのコンソールを開く authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. + + Open link? + alert title + Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - ユーザープロフィールを開く - authentication reason - - - Open-source protocol and code – anybody can run the servers. - プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -4462,13 +5187,22 @@ VPN を有効にする必要があります。 Or show this code No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + alert message PING count @@ -4505,6 +5239,10 @@ VPN を有効にする必要があります。 パスコードを設定しました! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show パスワードを表示する @@ -4535,13 +5273,8 @@ VPN を有効にする必要があります。 Pending No comment provided by engineer. - - People can connect to you only via the links you share. - あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 - No comment provided by engineer. - - - Periodically + + Periodic 定期的に No comment provided by engineer. @@ -4636,11 +5369,27 @@ Error: %@ パスフレーズを失くさないように保管してください。失くすと変更できなくなります。 No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface ポーランド語UI No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect サーバアドレスの証明証IDが正しくないかもしれません @@ -4651,16 +5400,15 @@ Error: %@ 添付を含めて、下書きを保存する。 No comment provided by engineer. - - Preset server - プレセットサーバ - No comment provided by engineer. - Preset server address プレセットサーバのアドレス No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview プレビュー @@ -4675,16 +5423,32 @@ Error: %@ プライバシーとセキュリティ No comment provided by engineer. + + Privacy for your customers. + 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 プライベートなファイル名 No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing No comment provided by engineer. @@ -4695,6 +5459,7 @@ Error: %@ Private notes + プライベートノート name of notes to self @@ -4719,14 +5484,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password プロフィールのパスワード @@ -4739,7 +5496,7 @@ Error: %@ Profile update will be sent to your contacts. 連絡先にプロフィール更新のお知らせが届きます。 - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4761,6 +5518,10 @@ Error: %@ メッセージへのリアクションは禁止されています。 No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. No comment provided by engineer. @@ -4822,6 +5583,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications プッシュ通知 @@ -4859,28 +5624,23 @@ Enable in *Network & servers* settings. 続きを読む No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。 - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/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. - GitHubリポジトリで詳細をご確認ください。 - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - 詳しくは[GitHubリポジトリ](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。 + 詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。 No comment provided by engineer. @@ -4996,11 +5756,23 @@ Enable in *Network & servers* settings. 電池使用量低減 No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject 拒否 reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5027,6 +5799,10 @@ Enable in *Network & servers* settings. 削除 No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5086,6 +5862,46 @@ Enable in *Network & servers* settings. 返信 chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required 必須 @@ -5165,6 +5981,10 @@ Enable in *Network & servers* settings. 開示する chat item action + + Review conditions + No comment provided by engineer. + Revoke 取り消す @@ -5194,6 +6014,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5205,17 +6029,18 @@ Enable in *Network & servers* settings. Save 保存 - chat item action + alert button +chat item action Save (and notify contacts) 保存(連絡先に通知) - No comment provided by engineer. + alert button Save and notify contact 保存して、連絡先にに知らせる - No comment provided by engineer. + alert button Save and notify group members @@ -5231,21 +6056,15 @@ Enable in *Network & servers* settings. グループプロファイルの保存と更新 No comment provided by engineer. - - Save archive - アーカイブを保存 - No comment provided by engineer. - - - Save auto-accept settings - 自動受け入れ設定を保存する - No comment provided by engineer. - Save group profile グループプロフィールの保存 No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat パスフレーズをを保存して、チャットを開始 @@ -5259,7 +6078,7 @@ Enable in *Network & servers* settings. Save preferences? この設定でよろしいですか? - No comment provided by engineer. + alert title Save profile password @@ -5274,18 +6093,17 @@ Enable in *Network & servers* settings. Save servers? サーバを保存しますか? - No comment provided by engineer. - - - Save settings? - 設定を保存しますか? - No comment provided by engineer. + alert title Save welcome message? ウェルカムメッセージを保存しますか? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5303,6 +6121,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5376,6 +6198,10 @@ Enable in *Network & servers* settings. 選択 chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5459,9 +6285,8 @@ Enable in *Network & servers* settings. 通知を送信する No comment provided by engineer. - - Send notifications: - 通知を送信する: + + Send private reports No comment provided by engineer. @@ -5485,7 +6310,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. 送信者がファイル転送をキャンセルしました。 - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5571,6 +6396,14 @@ Enable in *Network & servers* settings. Sent via proxy No comment provided by engineer. + + Server + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5583,6 +6416,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password キューを作成するにはサーバーの認証が必要です。パスワードを確認してください @@ -5632,6 +6477,10 @@ Enable in *Network & servers* settings. 1日に設定 No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… 連絡先の名前を設定… @@ -5651,6 +6500,10 @@ Enable in *Network & servers* settings. システム認証の代わりに設定します。 No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode パスコードを設定する @@ -5680,6 +6533,10 @@ Enable in *Network & servers* settings. 設定 No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5687,22 +6544,35 @@ Enable in *Network & servers* settings. Share 共有する - chat item action + alert action +chat item action Share 1-time link 使い捨てのリンクを共有 No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address アドレスを共有する No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? アドレスを連絡先と共有しますか? - No comment provided by engineer. + alert title Share from other apps. @@ -5713,6 +6583,10 @@ Enable in *Network & servers* settings. リンクを送る No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -5726,6 +6600,10 @@ Enable in *Network & servers* settings. 連絡先と共有する No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -5776,6 +6654,10 @@ Enable in *Network & servers* settings. SimpleXアドレス No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat のセキュリティは Trail of Bits によって監査されました。 @@ -5806,6 +6688,18 @@ Enable in *Network & servers* settings. SimpleXアドレス No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX連絡先アドレス @@ -5826,8 +6720,8 @@ Enable in *Network & servers* settings. SimpleXリンク chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -5839,6 +6733,10 @@ Enable in *Network & servers* settings. SimpleX使い捨て招待リンク simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode シークレットモードの簡素化 @@ -5867,6 +6765,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -5880,11 +6782,21 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody 誰か notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. No comment provided by engineer. @@ -5925,11 +6837,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - チャットを停止してデータベースアクションを有効にします - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. データベースのエクスポート、読み込み、削除するにはチャットを閉じてからです。チャットを閉じると送受信ができなくなります。 @@ -5958,17 +6865,21 @@ Enable in *Network & servers* settings. Stop sharing 共有を停止 - No comment provided by engineer. + alert action Stop sharing address? アドレスの共有を停止しますか? - No comment provided by engineer. + alert title Stopping chat No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong blur media @@ -5995,6 +6906,14 @@ Enable in *Network & servers* settings. Simplex Chatを支援 No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System システム @@ -6014,6 +6933,10 @@ Enable in *Network & servers* settings. TCP接続タイムアウト No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6029,11 +6952,19 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture 写真を撮影 No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button ボタンをタップ @@ -6068,13 +6999,17 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + file error alert title Test failed at step %@. テストはステップ %@ で失敗しました。 server test failure + + Test notifications + No comment provided by engineer. + Test server テストサーバ @@ -6088,7 +7023,7 @@ Enable in *Network & servers* settings. Tests failed! テストは失敗しました! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6105,11 +7040,6 @@ Enable in *Network & servers* settings. ユーザーに感謝します – Weblate 経由で貢献してください! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6122,6 +7052,10 @@ It can happen because of some bug or when the connection is compromised.アプリは、メッセージや連絡先のリクエストを受信したときに通知することができます - 設定を開いて有効にしてください。 No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6135,6 +7069,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! 承認済の接続がキャンセルされます! @@ -6155,6 +7093,11 @@ It can happen because of some bug or when the connection is compromised.暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります! No comment provided by engineer. + + The future of messaging + 次世代のプライバシー・メッセンジャー + No comment provided by engineer. + The hash of the previous message is different. 以前のメッセージとハッシュ値が異なります。 @@ -6178,19 +7121,17 @@ 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. - - The next generation of private messaging - 次世代のプライバシー・メッセンジャー - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. 古いデータベースは移行時に削除されなかったので、削除することができます。 No comment provided by engineer. - - The profile is only shared with your contacts. - プロフィールは連絡先にしか共有されません。 + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6208,14 +7149,26 @@ It can happen because of some bug or when the connection is compromised.現在のチャットプロフィールの新しい接続のサーバ **%@**。 No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. これらの設定は現在のプロファイル **%@** 用です。 @@ -6236,6 +7189,10 @@ It can happen because of some bug or when the connection is compromised.選択中の以前の送受信したメッセージが削除されます (※元に戻せません※)。数分かかります。 No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. あなたのプロフィール、連絡先、メッセージ、ファイルが完全削除されます (※元に戻せません※)。 @@ -6274,10 +7231,18 @@ It can happen because of some bug or when the connection is compromised.This is your own one-time link! 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. + No comment provided by engineer. + 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. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. この設定は現在のチャットプロフィール **%@** のメッセージに適用されます。 @@ -6306,9 +7271,8 @@ It can happen because of some bug or when the connection is compromised.新規に接続する場合 No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。 + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6327,6 +7291,23 @@ You will be prompted to complete authentication before this feature is enabled.< オンにするには、認証ステップが行われます。 No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。 + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. 音声メッセージを録音する場合は、マイクの使用を許可してください。 @@ -6337,11 +7318,19 @@ You will be prompted to complete authentication before this feature is enabled.< 非表示のプロフィールを表示するには、**チャット プロフィール** ページの検索フィールドに完全なパスワードを入力します。 No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. インスタント プッシュ通知をサポートするには、チャット データベースを移行する必要があります。 No comment provided by engineer. + + 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. エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。 @@ -6355,6 +7344,10 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle incognito when connecting. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity No comment provided by engineer. @@ -6421,6 +7414,10 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state 予期しない移行状態 @@ -6468,7 +7465,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6503,13 +7500,17 @@ To connect, please ask your contact to create another connection link and check Unmute ミュート解除 - swipe action + notification label action Unread 未読 swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -6533,6 +7534,10 @@ To connect, please ask your contact to create another connection link and check Update settings? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. 設定を更新すると、全サーバにクライントの再接続が行われます。 @@ -6568,16 +7573,32 @@ To connect, please ask your contact to create another connection link and check Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts .onionホストを使う No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX チャット サーバーを使用しますか? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat チャット @@ -6588,6 +7609,14 @@ To connect, please ask your contact to create another connection link and check 現在のプロファイルを使用する No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections 新しい接続に使う @@ -6624,6 +7653,14 @@ To connect, please ask your contact to create another connection link and check サーバを使う No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6632,15 +7669,18 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. - - User profile - ユーザープロフィール + + Use web port No comment provided by engineer. User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX チャット サーバーを使用する。 @@ -6705,11 +7745,19 @@ To connect, please ask your contact to create another connection link and check 1GBまでのビデオとファイル No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code セキュリティコードを確認 No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -6724,8 +7772,8 @@ To connect, please ask your contact to create another connection link and check このチャットでは音声メッセージが使用禁止です。 No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. このグループでは音声メッセージが使用禁止です。 No comment provided by engineer. @@ -6812,9 +7860,8 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - 接続が要求されたら、それを受け入れるか拒否するかを選択できます。 + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -6853,7 +7900,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -6877,11 +7924,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - You - あなた - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. @@ -6906,6 +7948,10 @@ To connect, please ask your contact to create another connection link and check すでに %@ に接続されています。 No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. @@ -6958,6 +8004,10 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure servers via settings. + No comment provided by engineer. + You can create it later 後からでも作成できます @@ -6984,6 +8034,7 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. + 設定でSimpleXの連絡先に表示させることができます。 No comment provided by engineer. @@ -6995,6 +8046,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. 設定からロック画面の通知プレビューを設定できます。 @@ -7010,11 +8065,6 @@ Repeat join request? このアドレスを連絡先と共有して、**%@** に接続できるようにすることができます。 No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - アドレスをリンクやQRコードとして共有することで、誰でも接続することができます。 - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app アプリの設定/データベースから、またはアプリを再起動することでチャットを開始できます @@ -7036,23 +8086,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! メッセージを送信できませんでした! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - あなたはメッセージの受信に使用するサーバーを制御し、連絡先はあなたがメッセージの送信に使用するサーバーを使用することができます。 - 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 already requested connection via this address! No comment provided by engineer. @@ -7114,6 +8164,10 @@ Repeat connection request? グループの招待を送りました No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! グループのホスト端末がオンラインになったら、接続されます。後でチェックするか、しばらくお待ちください! @@ -7147,6 +8201,10 @@ Repeat connection request? ミュートされたプロフィールがアクティブな場合でも、そのプロフィールからの通話や通知は引き続き受信します。 No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. このグループからのメッセージが届かなくなります。チャットの履歴が残ります。 @@ -7167,31 +8225,16 @@ Repeat connection request? シークレットモードのプロフィールでこのグループに参加しています。メインのプロフィールを守るために、招待することができません No comment provided by engineer. - - Your %@ servers - あなたの %@ サーバー - No comment provided by engineer. - Your ICE servers あなたのICEサーバ No comment provided by engineer. - - Your SMP servers - あなたのSMPサーバ - No comment provided by engineer. - Your SimpleX address あなたのSimpleXアドレス No comment provided by engineer. - - Your XFTP servers - あなたのXFTPサーバ - No comment provided by engineer. - Your calls あなたの通話 @@ -7207,11 +8250,19 @@ Repeat connection request? チャット データベースは暗号化されていません - 暗号化するにはパスフレーズを設定してください。 No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles あなたのチャットプロフィール No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). 連絡先が現在サポートされている最大サイズ (%@) より大きいファイルを送信しました。 @@ -7227,6 +8278,10 @@ Repeat connection request? 連絡先は接続されたままになります。 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. 現在のチャット データベースは削除され、インポートされたデータベースに置き換えられます。 @@ -7256,33 +8311,34 @@ Repeat connection request? あなたのプロファイル **%@** が共有されます。 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 only shared with your contacts. + プロフィールは連絡先にしか共有されません。 No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。 + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + プロフィールはデバイスに保存され、連絡先とのみ共有されます。 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 random profile あなたのランダム・プロフィール No comment provided by engineer. - - Your server - あなたのサーバ - No comment provided by engineer. - Your server address あなたのサーバアドレス No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings あなたの設定 @@ -7323,6 +8379,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 受けた通話 call status + + accepted invitation + chat list item title + admin 管理者 @@ -7355,6 +8415,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 and %lld other events No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts No comment provided by engineer. @@ -7388,7 +8452,8 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7501,7 +8566,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 connecting… 接続待ち… - chat list item title + No comment provided by engineer. connection established @@ -7554,7 +8619,8 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 default (%@) デフォルト (%@) - pref value + delete after time +pref value default (no) @@ -7679,11 +8745,6 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 エラー No comment provided by engineer. - - event happened - イベント発生 - No comment provided by engineer. - expired No comment provided by engineer. @@ -7848,19 +8909,19 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 %@ によってモデレートされた marked deleted chat item preview text + + moderator + member role + months time unit - - mute - No comment provided by engineer. - never 一度も - No comment provided by engineer. + delete after time new message @@ -7891,8 +8952,8 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 off オフ enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -7931,6 +8992,14 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 P2P No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -7945,6 +9014,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 確認を受け取りました… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call 拒否した通話 @@ -7973,6 +9046,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 あなたを除名しました rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. @@ -8059,10 +9136,6 @@ last received msg: %2$@ unknown status No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected No comment provided by engineer. @@ -8219,7 +9292,7 @@ last received msg: %2$@
- +
@@ -8255,7 +9328,7 @@ last received msg: %2$@
- +
@@ -8275,9 +9348,36 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
@@ -8296,7 +9396,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/contents.json b/apps/ios/SimpleX Localizations/ja.xcloc/contents.json index 604a21be97..ce6052fc44 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/ja.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "ja", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index 511536427d..019f63cbc0 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -24,220 +24,264 @@ No comment provided by engineer. - + ( + ( No comment provided by engineer. - + (can be copied) + (복사 가능) No comment provided by engineer. - + !1 colored! + !1 색상 적용됨! No comment provided by engineer. - + #secret# + #비밀# No comment provided by engineer. - + %@ + %@ No comment provided by engineer. - + %@ %@ + %@ %@ No comment provided by engineer. - + %@ / %@ + %@ / %@ No comment provided by engineer. - + %@ is connected! + %@이(가) 연결되었습니다! notification title - + %@ is not verified + %@은(는) 인증되지 않았습니다 No comment provided by engineer. - + %@ is verified + %@ 은(는) 인증되었습니다 No comment provided by engineer. - + %@ wants to connect! + %@ 연결을 원함! notification title - + %d days + %d 일 message ttl - + %d hours + %d 시간 message ttl - + %d min + %d 분 message ttl - + %d months + %d 개월 message ttl - + %d sec + %d 초 message ttl - + %d skipped message(s) + 건너뛰기 메시지 %d개 integrity error chat item - + %lld + %lld No comment provided by engineer. - + %lld %@ + %lld %@ No comment provided by engineer. - + %lld contact(s) selected + %lld명의 연락처 선택됨 No comment provided by engineer. - + %lld file(s) with total size of %@ + 총 크기가 %@인 파일 %lld 개 No comment provided by engineer. - + %lld members + %lld명의 멤버 No comment provided by engineer. - + %lld second(s) + %lld 초 No comment provided by engineer. - + %lldd + %lldd No comment provided by engineer. - + %lldh + %lldh No comment provided by engineer. - + %lldk + %lldk No comment provided by engineer. - + %lldm + %lldm No comment provided by engineer. - + %lldmth + %lldmth No comment provided by engineer. %llds + No comment provided by engineer. %lldw No comment provided by engineer. - + ( + ( No comment provided by engineer. - + ) - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. + ) No comment provided by engineer. **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. + **비공개**: 20분마다 새로운 메시지를 확인합니다. 푸시 서버에는 장치 토큰만 공유됩니다. 연락처 수나 메세지 메타데이터가 표시되지 않습니다. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. + **비공개**: SimpleX 채팅 푸시 서버를 사용하지 마세요. 앱은 사용 빈도에 따라 시스템이 허용하는 백그라운드에서 메세지를 확인합니다. No comment provided by engineer. **Paste received link** or open it in the browser and tap **Open in mobile app**. No comment provided by engineer. - + **Please note**: you will NOT be able to recover or change passphrase if you lose it. + **참고**: 비밀번호를 분실하면 복구하거나 변경할 수 없습니다. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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 채팅 푸시 서버로 전송되지만 메세지 내용, 크기 또는 발신자가 표시되지 않습니다. No comment provided by engineer. **Scan QR code**: to connect to your contact in person or via video call. No comment provided by engineer. - + **Warning**: Instant push notifications require passphrase saved in Keychain. + **경고**: 즉각적인 푸시 알림은 암호문을 키체인에 저장해야 합니다. No comment provided by engineer. - + **e2e encrypted** audio call + **e2e** 오디오 통화 No comment provided by engineer. - + **e2e encrypted** video call + **e2e 암호화** 영상 통화 No comment provided by engineer. \*bold* + No comment provided by engineer. - + , + , No comment provided by engineer. - + . + . No comment provided by engineer. - + 1 day + 1일 message ttl - + 1 hour + 1시간 message ttl - + 1 month + 1개월 message ttl - + 1 week + 1주 message ttl 2 weeks message ttl - + 6 + 6 No comment provided by engineer. - + : + : No comment provided by engineer. - + A new contact + 새로운 연결 notification title @@ -248,29 +292,34 @@ A random profile will be sent to your contact No comment provided by engineer. - + A separate TCP connection will be used **for each chat profile you have in the app**. + 앱에 있는 각 채팅 프로필**마다 별도의 TCP 연결이 사용됩니다. No comment provided by engineer. - + A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. + 각 연락처 및 그룹 구성원**마다 별도의 TCP 연결이 사용됩니다. +**참고**: 연결이 많으면 배터리와 트래픽 소비가 상당히 증가하고 일부 연결이 실패할 수 있습니다. No comment provided by engineer. About SimpleX No comment provided by engineer. - + About SimpleX Chat + SimpleX Chat에 대하여 No comment provided by engineer. Accent color No comment provided by engineer. - + Accept + 승인 accept contact request via notification accept incoming call via notification @@ -278,12 +327,14 @@ Accept contact No comment provided by engineer. - + Accept contact request from %@? + %@의 연락 요청을 수락하시겠습니까? notification body - + Accept incognito + 인정하지 않음 No comment provided by engineer. @@ -294,192 +345,233 @@ Add preset servers No comment provided by engineer. - + Add profile + 프로필 추가하기 No comment provided by engineer. - + Add servers by scanning QR codes. + QR 코드를 스캔하여 서버를 추가합니다. No comment provided by engineer. - + Add server + 서버 추가하기 No comment provided by engineer. - + Add to another device + 다른 장치에 추가하기 No comment provided by engineer. - + Add welcome message + 환영 메세지 추가하기 No comment provided by engineer. - + Admins can create the links to join groups. + 관리자는 그룹에 가입할 수 있는 링크를 만들 수 있습니다. No comment provided by engineer. - + Advanced network settings + 고급 네트워크 설정 No comment provided by engineer. - + All chats and messages will be deleted - this cannot be undone! + 모든 채팅과 메세지가 삭제됩니다. - 수정 불가능! No comment provided by engineer. - + All group members will remain connected. + 모든 그룹 구성원은 연결 상태를 유지합니다. No comment provided by engineer. - + All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. + 모든 메세지가 삭제됩니다 - 수정할 수 없습니다! 메세지는 오직 당신만을 위해 삭제될 것입니다. No comment provided by engineer. All your contacts will remain connected No comment provided by engineer. - + Allow + 승인 No comment provided by engineer. - + Allow disappearing messages only if your contact allows it to you. + 연락처가 메세지를 허용하는 경우에만 메세지 삭제를 허용합니다. No comment provided by engineer. Allow irreversible message deletion only if your contact allows it to you. No comment provided by engineer. - + Allow sending direct messages to members. + 회원에게 직접 메시지를 보낼 수 있습니다. No comment provided by engineer. - + Allow sending disappearing messages. + 사라지는 메시지를 보내는 것을 허용합니다. No comment provided by engineer. Allow to irreversibly delete sent messages. No comment provided by engineer. - + Allow to send voice messages. + 음성 메세지를 보낼 수 있습니다. No comment provided by engineer. - + Allow voice messages only if your contact allows them. + 연락처가 음성 메세지를 허용하는 경우에만 음성 메세지를 허용합니다. No comment provided by engineer. - + Allow voice messages? + 음성 메세지를 허용 하겠습니까? No comment provided by engineer. Allow your contacts to irreversibly delete sent messages. No comment provided by engineer. - + Allow your contacts to send disappearing messages. + 연락처가 사라지는 메시지를 보낼 수 있도록 허용합니다. No comment provided by engineer. - + Allow your contacts to send voice messages. + 연락처가 음성 메시지를 보낼 수 있도록 허용합니다. No comment provided by engineer. - + Already connected? + 이미 연결되었나요? No comment provided by engineer. - + Always use relay + 항상 릴레이 사용 No comment provided by engineer. - + Answer call + 응답 전화 No comment provided by engineer. - + App build: %@ + 앱 빌드: %@ No comment provided by engineer. - + App icon + 앱 아이콘 No comment provided by engineer. - + App version + 앱 버전 No comment provided by engineer. - + App version: v%@ + 앱 버전: v%@ No comment provided by engineer. - + Appearance + 출석 No comment provided by engineer. - + Attach + 첨부 No comment provided by engineer. - + Audio & video calls + 음성 & 영상 통화 No comment provided by engineer. - + Audio and video calls + 음성 및 영상 통화 No comment provided by engineer. - + Authentication failed + 인증 실패 No comment provided by engineer. - + Authentication is required before the call is connected, but you may miss calls. + 통화가 연결되기 전에 인증이 필요하지만, 통화를 놓칠 수 있습니다. No comment provided by engineer. - + Authentication unavailable + 인증 사용 불가 No comment provided by engineer. - + Auto-accept contact requests + 연락처 요청 자동 수락 No comment provided by engineer. - + Auto-accept images + 이미지 자동 수락 No comment provided by engineer. Automatically No comment provided by engineer. - + Back + 뒤로가기 No comment provided by engineer. Both you and your contact can irreversibly delete sent messages. No comment provided by engineer. - + Both you and your contact can send disappearing messages. + 당신과 당신의 연락처 모두 사라지는 메시지를 보낼 수 있습니다. No comment provided by engineer. - + Both you and your contact can send voice messages. + 당신과 당신의 연락처 모두 음성 메시지를 보낼 수 있습니다. 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. - + Call already ended! + 통화가 이미 종료되었습니다! No comment provided by engineer. - + Calls + 통화 No comment provided by engineer. @@ -491,8 +583,9 @@ 주소를 초대할 수 없습니다. No comment provided by engineer. - + Can't invite contacts! + 연락처를 초대할 수 없습니다! No comment provided by engineer. @@ -515,8 +608,9 @@ 변경 No comment provided by engineer. - + Change database passphrase? + 데이터베이스 암호 변경? No comment provided by engineer. @@ -544,16 +638,19 @@ 채팅 기록 보관함 No comment provided by engineer. - + Chat console + 채팅 콘솔 No comment provided by engineer. - + Chat database + 채팅 데이터베이스 No comment provided by engineer. - + Chat database deleted + 채팅 데이터베이스 삭제 No comment provided by engineer. @@ -561,80 +658,98 @@ 채팅 데이터베이스를 가져옴 No comment provided by engineer. - + Chat is running + 채팅이 실행 중입니다 No comment provided by engineer. - + Chat is stopped + 채팅이 중단되었습니다 No comment provided by engineer. - + Chat preferences + 채팅 환경설정 No comment provided by engineer. - + Chats + 채팅 No comment provided by engineer. - + Check server address and try again. + 서버 주소를 확인한 후 다시 시도합니다. No comment provided by engineer. - + Chinese and Spanish interface + 중국어 및 스페인어 환경 No comment provided by engineer. - + Choose file + 파일 선택 No comment provided by engineer. - + Choose from library + 라이브러리에서 선택 No comment provided by engineer. - + Clear + 정리 No comment provided by engineer. - + Clear conversation + 대화 삭제 No comment provided by engineer. - + Clear conversation? + 대화 삭제? No comment provided by engineer. - + Clear verification + 인증 삭제 No comment provided by engineer. Colors No comment provided by engineer. - + Compare security codes with your contacts. + 보안 코드를 연락처와 비교합니다. No comment provided by engineer. - + Configure ICE servers + ICE 서버 구성 No comment provided by engineer. - + Confirm + 확인 No comment provided by engineer. - + Confirm new passphrase… + 새 암호 확인… No comment provided by engineer. - + Confirm password + 비밀번호 확인 No comment provided by engineer. - + Connect + 연결 server test step @@ -645,8 +760,9 @@ Connect via group link? No comment provided by engineer. - + Connect via link + 링크를 통해 연결 No comment provided by engineer. @@ -657,174 +773,210 @@ Connect via one-time link? No comment provided by engineer. - + Connecting to server… + 서버에 연결중… No comment provided by engineer. - + Connecting to server… (error: %@) + 서버에 연결중...(오류: %@) No comment provided by engineer. - + Connection + 연결 No comment provided by engineer. - + Connection error + 연결 오류 No comment provided by engineer. - + Connection error (AUTH) + 연결 에러 (인증) No comment provided by engineer. Connection request No comment provided by engineer. - + Connection request sent! + 연결 요청이 전송되었습니다! No comment provided by engineer. - + Connection timeout + 연결 시간초과 No comment provided by engineer. - + Contact allows + 연락 가능 No comment provided by engineer. - + Contact already exists + 연결이 이미 존재 No comment provided by engineer. Contact and all messages will be deleted - this cannot be undone! No comment provided by engineer. - + Contact hidden: + 숨겨진 연락처: notification - + Contact is connected + 연락처가 연결되었습니다 notification Contact is not connected yet! No comment provided by engineer. - + Contact name + 연락처 이름 No comment provided by engineer. - + Contact preferences + 연락처 선호도 No comment provided by engineer. Contact requests No comment provided by engineer. - + Contacts can mark messages for deletion; you will be able to view them. + 연락처는 메세지를 삭제하도록 표시할 수 있으며, 이를 확인할 수 있습니다. No comment provided by engineer. - + Copy + 복사 chat item action Core built at: %@ No comment provided by engineer. - + Core version: v%@ + 코어 버전: v%@ No comment provided by engineer. - + Create + 생성 No comment provided by engineer. Create address No comment provided by engineer. - + Create group link + 그룹 링크 생성 No comment provided by engineer. - + Create link + 링크 생성 No comment provided by engineer. Create one-time invitation link No comment provided by engineer. - + Create queue + 큐 생성 server test step - + Create secret group + 비밀 그룹 생성 No comment provided by engineer. - + Create your profile + 프로필 생성 No comment provided by engineer. Created on %@ No comment provided by engineer. - + Current passphrase… + 현재 암호… No comment provided by engineer. - + Currently maximum supported file size is %@. + 현재 지원되는 최대 파일 크기는 %@입니다. No comment provided by engineer. - + Dark + 다크 No comment provided by engineer. - + Database ID + 데이터베이스 아이디 No comment provided by engineer. - + Database encrypted! + 데이터베이스 암호화됨! No comment provided by engineer. - + Database encryption passphrase will be updated and stored in the keychain. + 데이터베이스 암호화 키가 키체인에 저장됩니다. + No comment provided by engineer. - + Database encryption passphrase will be updated. + 데이터베이스 암호화 키가 업데이트됩니다. + No comment provided by engineer. - + Database error + 데이터베이스 오류 No comment provided by engineer. - + Database is encrypted using a random passphrase, you can change it. + 데이터베이스는 임의의 암호를 사용하여 암호화되므로 변경할 수 있습니다. No comment provided by engineer. - + Database is encrypted using a random passphrase. Please change it before exporting. + 데이터베이스는 임의의 암호를 사용하여 암호화됩니다. 내보내기 전에 변경하십시오. No comment provided by engineer. - + Database passphrase + 데이터베이스 암호화 키 No comment provided by engineer. - + Database passphrase & export + 데이터베이스 암호화 키 & 내보내기 No comment provided by engineer. @@ -1013,8 +1165,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1029,8 +1181,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1421,16 +1573,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1537,8 +1689,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1642,8 +1794,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -1961,8 +2113,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2013,8 +2165,9 @@ We will be adding server redundancy to prevent lost messages. Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. + 누구나 서버를 호스팅할 수 있습니다. No comment provided by engineer. @@ -2049,8 +2202,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2670,8 +2823,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2706,16 +2859,16 @@ We will be adding server redundancy to prevent lost messages. The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -2774,8 +2927,8 @@ We will be adding server redundancy to prevent lost messages. To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2968,8 +3121,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -3093,10 +3246,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -3792,6 +3941,1186 @@ SimpleX servers cannot see your profile. 새로운 멤버에게 최대 100개의 마지막 메시지 보내기. No comment provided by engineer. + + ## History + ## 기록 + + + ## In reply to + ## 에 대한 답변 + + + %@ downloaded + %@ 다운로드됨 + + + # %@ + # %@ + + + %@ and %@ + %@ 그리고 %@ + + + %1$@ at %2$@: + %2$@의 %1$@: + + + %@ connected + %@ 연결됨 + + + %@ (current): + %@ (현재): + + + %@ (current) + %@ (현재) + + + %@ and %@ connected + %@ 및 %@이(가) 연결되었습니다 + + + %@ server + %@서버 + + + %@ servers + %@서버들 + + + %@, %@ and %lld members + %@, %@ 과 %lld 멤버들 + + + %d file(s) are still being downloaded. + %d 개의 파일 다운로드중. + + + %d file(s) were deleted. + %d개의 파일이 삭제됨. + + + %d file(s) were not downloaded. + %d개의 파일이 다운로드 되지 않음. + + + %d weeks + %d 주 + + + %lld seconds + %lld 초 + + + **Create 1-time link**: to create and share a new invitation link. + **1회 링크 생성** : 새 초대 링크를 생성하고 공유합니다. + + + 1-time link + 일회성 링크 + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + 일회용 링크는 *한 번의 연락처로만* 사용할 수 있으며, 대면 또는 메신저를 통해 공유할 수 있습니다. + + + A few more things + 몇 가지 더 + + + Accept conditions + 조건 수락 + + + Accepted conditions + 수락된 조건 + + + Active connections + 연결 활성화 + + + %@ uploaded + %@업로드됨 + + + Accept connection request? + 연결 요청을 수락하시겠습니까? + + + %lld minutes + %lld 분 + + + **Warning**: the archive will be removed. + **경고**: 보관물이 제거됩니다. + + + 5 minutes + 5 분 + + + Abort changing address + 주소 변경 중단 + + + Acknowledgement errors + 확인 오류 + + + Abort + 중단 + + + %u messages failed to decrypt. + %u개의 메세지를 번역하는데 실패함. + + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + 연락처가 다른 사람과 공유할 수 있도록 프로필에 주소를 추가합니다. 프로필 업데이트가 연락처로 전송됩니다. + + + %lld messages blocked by admin + 관리자에 의해 차단된 %lld개의 메세지 + + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **참고**: 두 장치에서 동일한 데이터베이스를 사용하면 보안 보호를 위해 연결에서 메세지를 해독할 수 있습니다. + + + **Create group**: to create a new group. + **그룹 생성** : 새로운 그룹을 생성합니다. + + + %d file(s) failed to download. + %d개의 파일을 다운로드하는데 실패함. + + + %d messages not forwarded + %d개의 메세지가 전달되지 않음 + + + **Scan / Paste link**: to connect via a link you received. + **스캔/붙여넣기 링크**: 받은 링크를 통해 연결합니다. + + + About operators + 연산자 정보 + + + Address change will be aborted. Old receiving address will be used. + 주소 변경이 중단됩니다. 이전 수신 주소가 사용됩니다. + + + %@: + %@: + + + %lld messages blocked + %lld개의 메세지가 차단됨 + + + %lld messages marked deleted + 삭제된 메세지 %lld 개 + + + - more stable message delivery. +- a bit better groups. +- and more! + - 보다 안정적인 메세지 전달. +- 조금 더 나은 그룹. +- 그리고 더! + + + 0s + 0초 + + + 1 minute + 1분 + + + Abort changing address? + 주소 변경을 중단하시겠습니까? + + + 30 seconds + 30초 + + + - voice messages up to 5 minutes. +- custom time to disappear. +- editing history. + - 음성 메세지 최대 5분. +- 사라지는 맞춤형 시간. +- 편집 기록. + + + Add friends + 친구 추가 + + + Add team members + 팀원 추가하기 + + + Add your team members to the conversations. + 대화에 팀원을 추가하세요. + + + %u messages skipped. + 메세지 %u개를 건너뜀. + + + %@, %@ and %lld other members connected + %@, %@ 그리고 %lld 다른 멤버들이 연결됨 + + + %lld messages moderated by %@ + %@ 에 의해 중재된 %lld 개의 메세지 + + + %lld new interface languages + %lld개의 새로운 인터페이스 언어 + + + %1$@, %2$@ + %1$@, %2$@ + + + - optionally notify deleted contacts. +- profile names with spaces. +- and more! + - 선택적으로 삭제된 연락처를 통지합니다. +- 공백이 있는 프로필 이름. +- 그리고 더! + + + <p>Hi!</p> +<p><a href="%@">Connect to me via SimpleX Chat</a></p> + <p>안녕하세요!/p> +<p><a href="%@">SimpleX 채팅을 통해 저에게 연결하세요 </a></p> + + + A new random profile will be shared. + 새로운 랜덤 프로필이 공유될 것입니다. + + + Acknowledged + 인정된 + + + Additional accent 2 + 추가 악센트2 + + + Added media & file servers + 미디어 및 파일 서버 추가 + + + Added message servers + 추가된 메세지 서버 + + + Additional accent + 추가 악센트 + + + Additional secondary + 추가적 보조 + + + Address + 주소 + + + Address or 1-time link? + 주소 또는 일회성 링크? + + + Address settings + 주소 세팅 + + + Admins can block a member for all. + 관리자는 모두를 위해 회원을 차단할 수 있습니다. + + + %lld group events + %lld개의 그룹 이벤트 + + + All app data is deleted. + 모든 앱 데이터가 삭제됩니다. + + + All data is erased when it is entered. + 입력하면 모든 데이터가 삭제됩니다. + + + 0 sec + 0 초 + + + (this device v%@) + (이 장치 v%@) + + + (new) + (새로운) + + + Advanced settings + 고급 설정 + + + All data is kept private on your device. + 모든 데이터는 기기에서 비공개로 유지됩니다. + + + All profiles + 전체 프로필 + + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + 모든 연락처, 대화 및 파일은 안전하게 암호화되어 구성된 XFTP 릴레이에 청크로 업로드됩니다. + + + Allow calls only if your contact allows them. + 허용된 연락처만 통화가 가능합니다. + + + Allow calls? + 통화 허용? + + + Allow downgrade + 강등 허용 + + + Allow irreversible message deletion only if your contact allows it to you. (24 hours) + 연락처가 허용하는 경우에만 수정 불가능한 메세지 삭제를 허용합니다. (24시간) + + + Allow to send files and media. + 파일과 미디어를 전송할 수 있습니다. + + + Archiving database + 보관된 데이터베이스 + + + Better calls + 더 나은 통화 + + + Block + 차단 + + + Conditions will be accepted for enabled operators after 30 days. + 30일 후에 활성화된 운영자에 대한 조건이 수락될 것입니다. + + + Conditions will be accepted on: %@. + 조건은 다음과 같습니다: %@. + + + Connect via one-time link + 일회성 링크를 통해 연결 + + + Connected desktop + 데스크톱과 연결됨 + + + Connected servers + 연결된 서버 + + + Connection security + 연결 보안 + + + Connection terminated + 종료된 연결 + + + Connection with desktop stopped + 데스크톱과의 연결이 중지됨 + + + Current conditions text couldn't be loaded, you can review conditions via this link: + 현재 조건 텍스트를 로드할 수 없습니다. 이 링크를 통해 조건을 검토할 수 있습니다: + + + Bad desktop address + 잘못된 데스크톱 주소 + + + Camera not available + 카메라가 사용 불가능합니다 + + + Custom time + 사용자 지정 시간 + + + Allow to irreversibly delete sent messages. (24 hours) + 보낸 메시지를 되돌릴 수 없도록 삭제합니다. (24시간) + + + Allow message reactions. + 메세지 응답 허용. + + + Allow your contacts adding message reactions. + 연락처가 메세지 응답을 추가하도록 허용합니다. + + + Already connecting! + 이미 연결 중입니다! + + + Already joining the group! + 그룹에 참가하는 중입니다! + + + Archive and upload + 기록 및 업로드 + + + Chat colors + 채팅 색깔 + + + Chat list + 채팅 목록 + + + Completed + 완료됨 + + + Copy error + 복사 오류 + + + Create SimpleX address + SimpleX 주소 생성 + + + Creating link… + 생성 링크… + + + Blocked by admin + 관리자에 의해 차단됨 + + + Connect to desktop + 데스크톱에 연결 + + + Created at + 에 생성됨 + + + Created at: %@ + 생성 위치: %@ + + + Change self-destruct passcode + 자기-파괴 비밀번호 변경 + + + Create file + 파일 생성 + + + Allow your contacts to irreversibly delete sent messages. (24 hours) + 연락처가 보낸 메세지를 되돌릴 수 없도록 삭제할 수 있도록 허용합니다. (24시간) + + + App data migration + 앱 데이터 이동 + + + Apply to + 적용 대상 + + + Block for all + 모두를 위한 차단 + + + Both you and your contact can add message reactions. + 당신과 당신의 연락처 모두 메세지 반응을 추가할 수 있습니다. + + + Calls prohibited! + 통화 금지! + + + Change self-destruct mode + 자기-파괴 모드 변경 + + + Contacts + 연락처 + + + Create group + 그룹 생성 + + + Both you and your contact can make calls. + 당신과 당신의 연락처 모두 전화를 걸 수 있습니다. + + + App passcode + 앱 비밀번호 + + + All your contacts will remain connected. Profile update will be sent to your contacts. + 당신의 모든 연락은 연결되어 있습니다. 프로필 업데이트가 모든 연락으로 전송됩니다. + + + App encrypts new local files (except videos). + 앱은 새로운 로컬 파일을 암호화합니다 (동영상 제외). + + + Chat preferences were changed. + 채팅 환경설정이 변경되었습니다. + + + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 + [데스크톱 앱]에 새로운 프로필 생성(https://simplex.chat/downloads/).💻 + + + Contact is deleted. + 연락처가 삭제되었습니다. + + + Continue + 계속 + + + Current Passcode + 현재 비밀번호 + + + An empty chat profile with the provided name is created, and the app opens as usual. + 제공된 이름으로 빈 채팅 프로필이 생성되고 앱이 정상적으로 열립니다. + + + Allow your contacts to call you. + 연락처가 전화할 수 있도록 허용합니다. + + + Allow sharing + 공유 허용 + + + Always use private routing. + 항상 개인 경로를 사용합니다. + + + Better user experience + 더 나은 사용자 경험 + + + Change lock mode + 잠금 모드 변경 + + + Allow message reactions only if your contact allows them. + 연락처가 메세지 응답을 허용하는 경우에만 메세지 응답을 허용합니다. + + + Better security ✅ + 더 나은 안전✅ + + + Both you and your contact can irreversibly delete sent messages. (24 hours) + 당신과 당신의 연락처 모두 보낸 메세지를 되돌릴 수 없습니다. (24시간) + + + Confirm contact deletion? + 연락처 삭제를 확인하시겠습니까? + + + Can't call contact + 연락처에 전화할 수 없습니다 + + + 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)에 감사드립니다! + + + Capacity exceeded - recipient did not receive previously sent messages. + 용량 초과 - 수신자가 이전에 보낸 메세지를 받지 못했습니다. + + + Chat + 채팅 + + + Connect to yourself? +This is your own one-time link! + 자신에게 연결할까요? +이것은 당신만의 일회성 링크입니다! + + + Choose _Migrate from another device_ on the new device and scan QR code. + 새 기기에서 _다른 기기에서 이동_을 선택하고 QR 코드를 스캔합니다. + + + Connecting to desktop + 데스크톱에 연결중 + + + Connect with %@ + %@와 연결 + + + Archived contacts + 보관된 연락처 + + + Better message dates. + 더 나은 메세지 날짜. + + + Better networking + 더 나은 네트워킹 + + + Check messages when allowed. + 허용될 때 메시지를 확인합니다. + + + Compare file + 파일 비교 + + + Conditions will be automatically accepted for enabled operators on: %@. + 다음 조건은 활성화된 운영자에 대해 자동으로 수락됩니다: %@. + + + Confirm upload + 업로드 확인 + + + Connect incognito + 비밀 연결 + + + Connect to your friends faster. + 친구들과 더 빨리 연결하세요. + + + Connect to yourself? + 자신과 연결할까요? + + + Created + 생성됨 + + + Creating archive link + 기록 링크 생성하기 + + + Auto-accept + 자동 수락 + + + All new messages from %@ will be hidden! + %@로부터의 모든 새 메세지가 숨겨집니다! + + + Auto-accept settings + 자동-수락 설정 + + + Archive contacts to chat later. + 나중에 채팅할 연락처를 보관합니다. + + + Background + 배경 + + + Bad message hash + 잘못된 메세지 hash + + + Better groups + 더 나은 그룹 + + + Better messages + 더 나은 메세지 + + + Chunks downloaded + 다운로드된 청크 + + + Chunks deleted + 삭제된 청크 + + + Chunks uploaded + 업로드 된 청크 + + + Corner + 코너 + + + Correct name to %@? + %@의 정확한 이름은? + + + Create a group using a random profile. + 랜덤 프로필을 사용하여 그룹을 만듭니다. + + + Authentication cancelled + 인증 취소 + + + Confirm Passcode + 비밀번호 확인 + + + Confirm database upgrades + 데이터베이스 업그레이드 확인 + + + Blur media + 가려진 미디어 + + + Block group members + 그룹 구성원 차단 + + + Connected + 연결됨 + + + All messages will be deleted - this cannot be undone! + 모든 메세지가 삭제됩니다 - 수정할 수 없습니다! + + + All your contacts will remain connected. + 당신의 모든 연락은 계속 연결되어 있습니다. + + + Allow to send SimpleX links. + SinpleX 링크 전송 허용. + + + Bad message ID + 잘못된 메세지 ID + + + Black + 블랙 + + + Block member + 차단 구성원 + + + Connected to desktop + 데스크톱과 연결됨 + + + App passcode is replaced with self-destruct passcode. + 앱 비밀번호는 자체-파괴 비밀번호로 대체됩니다. + + + Apply + 적용 + + + Better notifications + 더 나은 공지 + + + Block member? + 차단 멤버? + + + Blur for better privacy. + 더 나은 개인정보를 위해 흐림. + + + Business address + 사업체 주소 + + + Business chats + 비즈니스 채팅 + + + Can't call member + 회원에게 전화할 수 없습니다 + + + Can't message member + 멤버에게 메세지를 보낼 수 없습니다 + + + Cancel migration + 이동 취소 + + + Change chat profiles + 채팅 프로필 변경 + + + Chat already exists + 채팅이 이미 존재합니다 + + + Chat already exists! + 채팅이 이미 존재합니다! + + + Chat database exported + 채팅 데이터베이스 내보내기 + + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + 채팅이 중지되었습니다. 이미 다른 장치에서 이 데이터베이스를 사용하고 있다면 채팅을 시작하기 전에 다시 전송해야 합니다. + + + Chat migrated! + 채팅 이동! + + + Chat theme + 채팅 테마 + + + Chat will be deleted for all members - this cannot be undone! + 채팅은 모든 회원에게 삭제됩니다 - 이는 되돌릴 수 없습니다! + + + Chat profile + 채팅 프로필 + + + Chat will be deleted for you - this cannot be undone! + 채팅은 삭제됩니다 - 되돌릴 수 없습니다! + + + Check messages every 20 min. + 20분마다 메시지를 확인합니다. + + + Color chats with the new themes. + 새로운 테마로 채팅을 색칠하세요. + + + Color mode + 색깔 모드 + + + Clear private notes? + 개인 메모를 지우시겠습니까? + + + Conditions accepted on: %@. + 조건이 수락됨: %@. + + + Conditions are accepted for the operator(s): **%@**. + 운영자의 조건이 허용됩니다: **%@**. + + + Conditions of use + 이용 조건 + + + Conditions will be accepted for operator(s): **%@**. + 운영자 조건이 수락됩니다: **%@**. + + + Conditions will be accepted for the operator(s): **%@**. + 운영자 조건이 수락됩니다.: **%@**. + + + Confirm that you remember database passphrase to migrate it. + 이동하는데에 필요한 데이터베이스 비밀번호를 기억하는지 확인합니다. + + + Connect to yourself? +This is your own SimpleX address! + 자신과 연결할까요? +이것은 당신의 SimpleX 주소입니다! + + + Connect via contact address + 연락처 주소로 연결 + + + Connecting + 연결중 + + + Connecting to contact, please wait or check later! + 연락처에 연결 중이니 기다려 주시거나 나중에 확인해 주세요! + + + Connection and servers status. + 연결 및 서버 상태. + + + Connection notifications + 연결 공지 + + + Connections + 연결 + + + Contact will be deleted - this cannot be undone! + 연락처가 삭제됩니다 - 취소할 수 없습니다! + + + Conversation deleted! + 대화가 삭제되었습니다! + + + Create 1-time link + 일회성 링크 생성 + + + Create profile + 프로필 생성 + + + Audio/video calls + 음성/영상 통화 + + + Audio/video calls are prohibited. + 음성/영상 통화는 금지되어 있습니다. + + + App session + 앱 세션 + + + Block member for all? + 모두를 위한 차단 멤버? + + + Cannot forward message + 메세지를 전달할 수 없습니다 + + + Confirm network settings + 네트워크 설정 확인 + + + Connect automatically + 자동으로 연결 + + + Confirm files from unknown servers. + 알 수 없는 서버에서 파일을 확인합니다. + + + Conditions are already accepted for these operator(s): **%@**. + 이 운영자들에 대한 조건은 이미 받아들여지고 있습니다: **%@**. + + + Contact deleted! + 연락처 삭제! + + + Current profile + 현재 프로필 + + + Customizable message shape. + 사용자 지정 가능한 메세지 형태. + + + %d seconds(s) + %d 초 + + + 1 year + 1년 + + + Add list + 리스트 추가 + + + Add to list + 리스트에 추가 + + + All + 모두 + + + Allow to report messsages to moderators. + 메시지를 신고하는것을 허용합니다. + + + Another reason + 다른 이유 + + + App group: + 앱 그룹: + + + Archive + 아카이브 + + + Archive report + 신고 아카이브 + + + Archive report? + 신고를 아카이브할까요? + + + Archive reports + 신고 아카이브 + + + Ask + 묻기 + + + Clear group? + 그룹을 비울까요? + + + Clear or delete group? + 그룹을 비우거나 삭제할까요? + + + Community guidelines violation + 커뮤니티 지침 위반 + + + Connection blocked + 연결 차단됨 + + + Connection is blocked by server operator: +%@ + 서버 관리자에 의해 연결이 차단되었습니다: +%@ + + + Connection not ready. + 연결 준비되지 않음. + + + Connection requires encryption renegotiation. + 연결에는 암호화 재협상이 필요합니다. + + + Content violates conditions of use + 내용은 사용 규정을 위반합니다 + + + Create list + 리스트 추가 + + + Database ID: %d + 데이터베이스 아이디: %d + + + Database IDs and Transport isolation option. + 데이터베이스 ID 및 전송 격리 옵션. + + + Database downgrade + 데이터베이스 다운그레이드 + + + Better groups performance + 더 나은 그룹 성능 + + + Confirmed + 확인함 + + + Active + 활성화됨 + + + Archive all reports? + 모든 신고를 아카이브할까요? + + + Businesses + 비즈니스 + + + Better privacy and security + 더 나은 프라이버시 및 보안 + + + Change automatic message deletion? + 자동 메시지 삭제를 변경할까요? + + + All chats will be removed from the list %@, and the list deleted. + 모든 채팅은 %@ 리스트에서 제거되고 리스트는 삭제됩니다. + + + All reports will be archived for you. + 모든 보고서는 사용자를 위해 보관됩니다. + + + Accent + 강조 + + + Archive %lld reports? + %lld 신고를 아카이브할까요? + + + - 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)! +- 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 명의 멤버). +- 더 빠르고 안정적입니다. + + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + 모든 메시지와 파일은 **종간단 암호화 (E2EE)**되며, 개인 메시지는 양자 보안이 적용됩니다. + + + Customize theme + 테마 사용자 지정 + + + Dark mode colors + 다크 모드 색상들 +
diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index 6df24149e9..0f795170c6 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -162,20 +162,16 @@ ) No comment provided by engineer.
- - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. No comment provided by engineer. @@ -187,8 +183,8 @@ **Turėkite omenyje**: jeigu prarasite slaptafrazę, NEBEGALĖSITE jos atkurti ar pakeisti. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. No comment provided by engineer. @@ -1033,8 +1029,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1049,8 +1045,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1417,16 +1413,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1513,8 +1509,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1614,8 +1610,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -1919,8 +1915,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -1971,8 +1967,8 @@ We will be adding server redundancy to prevent lost messages. Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2003,8 +1999,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2591,8 +2587,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2627,16 +2623,16 @@ We will be adding server redundancy to prevent lost messages. The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -2687,8 +2683,8 @@ We will be adding server redundancy to prevent lost messages. To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2873,8 +2869,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -2993,10 +2989,6 @@ To connect, please ask your contact to create another connection link and check You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. 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 15a8c01a64..4008c57ac0 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (kan gekopieerd worden) @@ -127,6 +100,16 @@ %@ is geverifieerd No comment provided by engineer. + + %@ server + %@ server + No comment provided by engineer. + + + %@ servers + %@ servers + No comment provided by engineer. + %@ uploaded %@ geüpload @@ -137,6 +120,11 @@ %@ wil verbinding maken! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ en %lld leden @@ -157,11 +145,36 @@ %d dagen time interval + + %d file(s) are still being downloaded. + %d bestand(en) worden nog gedownload. + forward confirmation reason + + + %d file(s) failed to download. + %d bestand(en) konden niet worden gedownload. + forward confirmation reason + + + %d file(s) were deleted. + %d bestand(en) zijn verwijderd. + forward confirmation reason + + + %d file(s) were not downloaded. + %d bestand(en) zijn niet gedownload. + forward confirmation reason + %d hours %d uren time interval + + %d messages not forwarded + %d berichten niet doorgestuurd + alert title + %d min %d min @@ -177,6 +190,11 @@ %d sec time interval + + %d seconds(s) + %d seconden + delete after time + %d skipped message(s) %d overgeslagen bericht(en) @@ -247,11 +265,6 @@ %lld nieuwe interface-talen No comment provided by engineer. - - %lld second(s) - %lld seconde(n) - No comment provided by engineer. - %lld seconds %lld seconden @@ -302,11 +315,6 @@ %u berichten zijn overgeslagen. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nieuw) @@ -317,33 +325,23 @@ (dit apparaat v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **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. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Nieuw contact toevoegen**: om uw eenmalige QR-code of link voor uw contact te maken. - No comment provided by engineer. - **Create group**: to create a new group. **Groep aanmaken**: om een nieuwe groep aan te maken. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Meer privé**: bekijk elke 20 minuten nieuwe berichten. Apparaattoken wordt gedeeld met de SimpleX Chat-server, maar niet hoeveel contacten of berichten u heeft. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt). No comment provided by engineer. @@ -357,11 +355,16 @@ **Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Aanbevolen**: apparaattoken en meldingen worden naar de SimpleX Chat-meldingsserver gestuurd, maar niet de berichtinhoud, -grootte of van wie het afkomstig is. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + **Link scannen/plakken**: om verbinding te maken via een link die u hebt ontvangen. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain. @@ -387,11 +390,6 @@ \*vetgedrukt* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - bewerkingsgeschiedenis. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -446,7 +439,8 @@ 1 day 1 dag - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 maand - time interval + delete after time +time interval 1 week 1 week - time interval + delete after time +time interval + + + 1 year + 1 jaar + delete after time + + + 1-time link + Eenmalige link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Eenmalige link die *slechts met één contactpersoon* kan worden gebruikt - deel persoonlijk of via een messenger. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 seconden No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -512,7 +518,7 @@ A separate TCP connection will be used **for each chat profile you have in the app**. - Er wordt een aparte TCP-verbinding gebruikt **voor elk chat profiel dat je in de app hebt**. + Er wordt een aparte TCP-verbinding gebruikt **voor elk chatprofiel dat je in de app hebt**. No comment provided by engineer. @@ -537,19 +543,14 @@ Adres wijziging afbreken? No comment provided by engineer. - - About SimpleX - Over SimpleX - No comment provided by engineer. - About SimpleX Chat Over SimpleX Chat No comment provided by engineer. - - About SimpleX address - Over SimpleX adres + + About operators + Over operatoren No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Accepteer accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Accepteer voorwaarden + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Accepteer incognito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Geaccepteerde voorwaarden + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Bevestigingsfouten No comment provided by engineer. + + Active + actief + token status text + Active connections Actieve verbindingen @@ -600,14 +616,14 @@ 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 contact - Contact toevoegen + + Add friends + Vrienden toevoegen No comment provided by engineer. - - Add preset servers - Vooraf ingestelde servers toevoegen + + Add list + Lijst toevoegen No comment provided by engineer. @@ -625,16 +641,41 @@ Servers toevoegen door QR-codes te scannen. No comment provided by engineer. + + Add team members + Teamleden toevoegen + No comment provided by engineer. + Add to another device Toevoegen aan een ander apparaat No comment provided by engineer. + + Add to list + Toevoegen aan lijst + No comment provided by engineer. + Add welcome message Welkom bericht toevoegen No comment provided by engineer. + + Add your team members to the conversations. + Voeg uw teamleden toe aan de gesprekken. + No comment provided by engineer. + + + Added media & file servers + Media- en bestandsservers toegevoegd + No comment provided by engineer. + + + Added message servers + Berichtservers toegevoegd + No comment provided by engineer. + Additional accent Extra accent @@ -660,6 +701,16 @@ Adres wijziging wordt afgebroken. Het oude ontvangstadres wordt gebruikt. No comment provided by engineer. + + Address or 1-time link? + Adres of eenmalige link? + No comment provided by engineer. + + + Address settings + Adres instellingen + No comment provided by engineer. + Admins can block a member for all. Beheerders kunnen een lid voor iedereen blokkeren. @@ -680,6 +731,11 @@ Geavanceerde instellingen No comment provided by engineer. + + All + alle + No comment provided by engineer. + All app data is deleted. Alle app-gegevens worden verwijderd. @@ -690,13 +746,18 @@ Alle chats en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Alle chats worden uit de lijst %@ verwijderd en de lijst wordt verwijderd. + alert message + All data is erased when it is entered. Alle gegevens worden bij het invoeren gewist. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Alle gegevens zijn privé op uw apparaat. No comment provided by engineer. @@ -705,6 +766,11 @@ Alle groepsleden blijven verbonden. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Alle berichten en bestanden worden **end-to-end versleuteld** verzonden, met post-quantumbeveiliging in directe berichten. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Alle berichten worden verwijderd. Dit kan niet ongedaan worden gemaakt! @@ -723,6 +789,15 @@ All profiles Alle profielen + profile dropdown + + + All reports will be archived for you. + Alle rapporten worden voor u gearchiveerd. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -767,7 +842,7 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) - Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) + Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) No comment provided by engineer. @@ -797,7 +872,12 @@ Allow to irreversibly delete sent messages. (24 hours) - Sta toe om verzonden berichten onomkeerbaar te verwijderen. (24 uur) + Sta toe om verzonden berichten definitief te verwijderen. (24 uur) + No comment provided by engineer. + + + Allow to report messsages to moderators. + Hiermee kunt u berichten rapporteren aan moderators. No comment provided by engineer. @@ -837,7 +917,7 @@ Allow your contacts to irreversibly delete sent messages. (24 hours) - Laat uw contacten verzonden berichten onomkeerbaar verwijderen. (24 uur) + Laat uw contacten verzonden berichten definitief verwijderen. (24 uur) No comment provided by engineer. @@ -880,11 +960,21 @@ Er wordt een leeg chatprofiel met de opgegeven naam gemaakt en de app wordt zoals gewoonlijk geopend. No comment provided by engineer. + + Another reason + Een andere reden + report reason + 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: %@ @@ -900,6 +990,11 @@ App versleutelt nieuwe lokale bestanden (behalve video's). No comment provided by engineer. + + App group: + App-groep: + No comment provided by engineer. + App icon App icon @@ -915,6 +1010,11 @@ De app-toegangscode wordt vervangen door een zelfvernietigings wachtwoord. No comment provided by engineer. + + App session + Appsessie + No comment provided by engineer. + App version App versie @@ -940,6 +1040,21 @@ Toepassen op No comment provided by engineer. + + Archive + Archief + No comment provided by engineer. + + + Archive %lld reports? + %lld rapporten archiveren? + No comment provided by engineer. + + + Archive all reports? + Alle rapporten archiveren? + No comment provided by engineer. + Archive and upload Archiveren en uploaden @@ -950,6 +1065,21 @@ Archiveer contacten om later te chatten. No comment provided by engineer. + + Archive report + Rapport archiveren + No comment provided by engineer. + + + Archive report? + Rapport archiveren? + No comment provided by engineer. + + + Archive reports + Rapporten archiveren + swipe action + Archived contacts Gearchiveerde contacten @@ -982,7 +1112,7 @@ Audio/video calls are prohibited. - Audio/video gesprekken zijn verboden. + Audio/video gesprekken zijn niet toegestaan. No comment provided by engineer. @@ -1020,6 +1150,11 @@ Afbeeldingen automatisch accepteren No comment provided by engineer. + + Auto-accept settings + Instellingen automatisch accepteren + alert title + Back Terug @@ -1045,11 +1180,26 @@ Onjuiste bericht hash No comment provided by engineer. + + Better calls + Betere gesprekken + No comment provided by engineer. + Better groups Betere groepen No comment provided by engineer. + + Better groups performance + Betere prestaties van groepen + No comment provided by engineer. + + + Better message dates. + Betere datums voor berichten. + No comment provided by engineer. + Better messages Betere berichten @@ -1060,6 +1210,26 @@ Beter netwerk No comment provided by engineer. + + Better notifications + Betere meldingen + No comment provided by engineer. + + + Better privacy and security + Betere privacy en veiligheid + No comment provided by engineer. + + + Better security ✅ + Betere beveiliging ✅ + No comment provided by engineer. + + + Better user experience + Betere gebruikerservaring + No comment provided by engineer. + Black Zwart @@ -1140,9 +1310,33 @@ 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. + + Business address + Zakelijk adres + No comment provided by engineer. + + + Business chats + Zakelijke chats + No comment provided by engineer. + + + Businesses + bedrijven + 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 chat profiel (standaard) of [via verbinding](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: +- 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. @@ -1157,7 +1351,7 @@ Calls prohibited! - Bellen verboden! + Bellen niet toegestaan! No comment provided by engineer. @@ -1193,7 +1387,8 @@ Cancel Annuleren - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1408,7 @@ Cannot receive file Kan bestand niet ontvangen - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1425,16 @@ Veranderen No comment provided by engineer. + + Change automatic message deletion? + Automatisch verwijderen van berichten wijzigen? + alert title + + + Change chat profiles + Gebruikersprofielen wijzigen + authentication reason + Change database passphrase? Wachtwoord database wijzigen? @@ -1274,11 +1479,21 @@ Change self-destruct passcode Zelfvernietigings code wijzigen authentication reason - set passcode view +set passcode view - - Chat archive - Gesprek archief + + Chat + Chat + No comment provided by engineer. + + + Chat already exists + Chat bestaat al + No comment provided by engineer. + + + Chat already exists! + Chat bestaat al! No comment provided by engineer. @@ -1341,20 +1556,50 @@ Gesprek voorkeuren No comment provided by engineer. + + Chat preferences were changed. + Chatvoorkeuren zijn gewijzigd. + alert message + + + Chat profile + Gebruikers profiel + No comment provided by engineer. + Chat theme Chat thema No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + De chat wordt voor alle leden verwijderd - dit kan niet ongedaan worden gemaakt! + 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! + No comment provided by engineer. + Chats - Gesprekken + Chats + No comment provided by engineer. + + + Check messages every 20 min. + Controleer uw berichten elke 20 minuten. + No comment provided by engineer. + + + Check messages when allowed. + Controleer berichten indien toegestaan. No comment provided by engineer. Check server address and try again. Controleer het server adres en probeer het opnieuw. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1651,16 @@ Gesprek wissen? No comment provided by engineer. + + Clear group? + Groep wissen? + No comment provided by engineer. + + + Clear or delete group? + Groep wissen of verwijderen? + No comment provided by engineer. + Clear private notes? Privénotities verwijderen? @@ -1426,6 +1681,11 @@ Kleur mode No comment provided by engineer. + + Community guidelines violation + Schending van de communityrichtlijnen + report reason + Compare file Bestand vergelijken @@ -1438,7 +1698,42 @@ Completed - voltooid + Voltooid + No comment provided by engineer. + + + Conditions accepted on: %@. + Voorwaarden geaccepteerd op: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Voorwaarden worden geaccepteerd voor de operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Voorwaarden zijn reeds geaccepteerd voor de volgende operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + Gebruiksvoorwaarden + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Voorwaarden worden geaccepteerd voor de operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Voorwaarden worden geaccepteerd op: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Voorwaarden worden automatisch geaccepteerd voor ingeschakelde operators op: %@. No comment provided by engineer. @@ -1446,9 +1741,9 @@ ICE servers configureren No comment provided by engineer. - - Configured %@ servers - %@ servers geconfigureerd + + Configure server operators + Serveroperators configureren No comment provided by engineer. @@ -1501,6 +1796,11 @@ Bevestig het uploaden No comment provided by engineer. + + Confirmed + Bevestigd + token status text + Connect Verbind @@ -1620,6 +1920,11 @@ Dit is uw eigen eenmalige link! Verbindings- en serverstatus. No comment provided by engineer. + + Connection blocked + Verbinding geblokkeerd + No comment provided by engineer. + Connection error Verbindingsfout @@ -1630,6 +1935,18 @@ Dit is uw eigen eenmalige link! Verbindingsfout (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + Verbinding is geblokkeerd door serveroperator: +%@ + No comment provided by engineer. + + + Connection not ready. + Verbinding nog niet klaar. + No comment provided by engineer. + Connection notifications Verbindingsmeldingen @@ -1640,6 +1957,16 @@ Dit is uw eigen eenmalige link! Verbindingsverzoek verzonden! No comment provided by engineer. + + Connection requires encryption renegotiation. + Verbinding vereist heronderhandeling over encryptie. + No comment provided by engineer. + + + Connection security + Beveiliging van de verbinding + No comment provided by engineer. + Connection terminated Verbinding beëindigd @@ -1715,6 +2042,11 @@ Dit is uw eigen eenmalige link! Contact personen kunnen berichten markeren voor verwijdering; u kunt ze wel bekijken. No comment provided by engineer. + + Content violates conditions of use + Inhoud schendt de gebruiksvoorwaarden + blocking reason + Continue Doorgaan @@ -1740,6 +2072,11 @@ Dit is uw eigen eenmalige link! Core versie: v% @ No comment provided by engineer. + + Corner + Hoek + No comment provided by engineer. + Correct name to %@? Juiste naam voor %@? @@ -1750,6 +2087,11 @@ Dit is uw eigen eenmalige link! Maak No comment provided by engineer. + + Create 1-time link + Eenmalige link maken + No comment provided by engineer. + Create SimpleX address Maak een SimpleX adres aan @@ -1760,11 +2102,6 @@ Dit is uw eigen eenmalige link! Maak een groep met een willekeurig profiel. No comment provided by engineer. - - Create an address to let people connect with you. - Maak een adres aan zodat mensen contact met je kunnen opnemen. - No comment provided by engineer. - Create file Bestand maken @@ -1785,6 +2122,11 @@ Dit is uw eigen eenmalige link! Maak link No comment provided by engineer. + + Create list + Maak een lijst + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Maak een nieuw profiel aan in [desktop-app](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2167,6 @@ Dit is uw eigen eenmalige link! Aangemaakt op: %@ copied message info - - Created on %@ - Gemaakt op %@ - No comment provided by engineer. - Creating archive link Archief link maken @@ -1845,6 +2182,11 @@ Dit is uw eigen eenmalige link! Huidige toegangscode No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + De tekst van de huidige voorwaarden kon niet worden geladen. U kunt de voorwaarden bekijken via deze link: + No comment provided by engineer. + Current passphrase… Huidige wachtwoord… @@ -1865,6 +2207,11 @@ Dit is uw eigen eenmalige link! Aangepaste tijd No comment provided by engineer. + + Customizable message shape. + Aanpasbare berichtvorm. + No comment provided by engineer. + Customize theme Thema aanpassen @@ -1951,7 +2298,7 @@ Dit is uw eigen eenmalige link! Database passphrase is required to open chat. - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. No comment provided by engineer. @@ -1996,8 +2343,8 @@ Dit is uw eigen eenmalige link! Delete Verwijderen - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,24 +2381,29 @@ Dit is uw eigen eenmalige link! Verwijderen en contact op de hoogte stellen No comment provided by engineer. - - Delete archive - Archief verwijderen + + Delete chat + Chat verwijderen No comment provided by engineer. - - Delete chat archive? - Chat archief verwijderen? + + Delete chat messages from your device. + Verwijder chatberichten van uw apparaat. No comment provided by engineer. Delete chat profile - Chat profiel verwijderen + Chatprofiel verwijderen No comment provided by engineer. Delete chat profile? - Chat profiel verwijderen? + Chatprofiel verwijderen? + No comment provided by engineer. + + + Delete chat? + Chat verwijderen? No comment provided by engineer. @@ -2091,7 +2443,7 @@ Dit is uw eigen eenmalige link! Delete files for all chat profiles - Verwijder bestanden voor alle chat profielen + Verwijder bestanden voor alle chatprofielen No comment provided by engineer. @@ -2129,6 +2481,11 @@ Dit is uw eigen eenmalige link! Link verwijderen? No comment provided by engineer. + + Delete list? + Lijst verwijderen? + alert title + Delete member message? Bericht van lid verwijderen? @@ -2142,7 +2499,7 @@ Dit is uw eigen eenmalige link! Delete messages Verwijder berichten - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2516,11 @@ Dit is uw eigen eenmalige link! Oude database verwijderen? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Maximaal 200 berichten verwijderen of modereren. + No comment provided by engineer. + Delete pending connection? Wachtende verbinding verwijderen? @@ -2174,6 +2536,11 @@ Dit is uw eigen eenmalige link! Wachtrij verwijderen server test step + + Delete report + Rapport verwijderen + No comment provided by engineer. + Delete up to 20 messages at once. Verwijder maximaal 20 berichten tegelijk. @@ -2209,6 +2576,11 @@ Dit is uw eigen eenmalige link! Verwijderingsfouten No comment provided by engineer. + + Delivered even when Apple drops them. + Geleverd ook als Apple ze verliest + No comment provided by engineer. + Delivery Bezorging @@ -2309,9 +2681,14 @@ Dit is uw eigen eenmalige link! Directe berichten chat feature - - Direct messages between members are prohibited in this group. - Directe berichten tussen leden zijn verboden in deze groep. + + Direct messages between members are prohibited in this chat. + Directe berichten tussen leden zijn in deze chat niet toegestaan. + No comment provided by engineer. + + + Direct messages between members are prohibited. + Directe berichten tussen leden zijn niet toegestaan. No comment provided by engineer. @@ -2324,6 +2701,16 @@ Dit is uw eigen eenmalige link! SimpleX Vergrendelen uitschakelen authentication reason + + Disable automatic message deletion? + Automatisch verwijderen van berichten uitschakelen? + alert title + + + Disable delete messages + Berichten verwijderen uitschakelen + alert button + Disable for all Uitschakelen voor iedereen @@ -2346,12 +2733,12 @@ Dit is uw eigen eenmalige link! Disappearing messages are prohibited in this chat. - Verdwijnende berichten zijn verboden in dit gesprek. + Verdwijnende berichten zijn niet toegestaan in dit gesprek. No comment provided by engineer. - - Disappearing messages are prohibited in this group. - Verdwijnende berichten zijn verboden in deze groep. + + Disappearing messages are prohibited. + Verdwijnende berichten zijn niet toegestaan. No comment provided by engineer. @@ -2409,6 +2796,16 @@ Dit is uw eigen eenmalige link! Stuur geen geschiedenis naar nieuwe leden. No comment provided by engineer. + + Do not use credentials with proxy. + Gebruik geen inloggegevens met proxy. + No comment provided by engineer. + + + Documents: + Documenten: + No comment provided by engineer. + Don't create address Maak geen adres aan @@ -2419,11 +2816,21 @@ Dit is uw eigen eenmalige link! Niet inschakelen No comment provided by engineer. + + Don't miss important messages. + Mis geen belangrijke berichten. + No comment provided by engineer. + Don't show again Niet meer weergeven No comment provided by engineer. + + Done + Klaar + No comment provided by engineer. + Downgrade and open chat Downgraden en chat openen @@ -2432,7 +2839,8 @@ Dit is uw eigen eenmalige link! Download Downloaden - chat item action + alert button +chat item action Download errors @@ -2449,6 +2857,11 @@ Dit is uw eigen eenmalige link! Download bestand server test step + + Download files + ‐Bestanden downloaden + alert action + Downloaded Gedownload @@ -2479,6 +2892,11 @@ Dit is uw eigen eenmalige link! Duur No comment provided by engineer. + + E2E encrypted notifications. + E2E versleutelde meldingen. + No comment provided by engineer. + Edit Bewerk @@ -2499,6 +2917,11 @@ Dit is uw eigen eenmalige link! Inschakelen (overschrijvingen behouden) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + Schakel Flux in bij Netwerk- en serverinstellingen voor betere privacy van metagegevens. + No comment provided by engineer. + Enable SimpleX Lock SimpleX Vergrendelen inschakelen @@ -2512,7 +2935,7 @@ Dit is uw eigen eenmalige link! Enable automatic message deletion? Automatisch verwijderen van berichten aanzetten? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3062,11 @@ Dit is uw eigen eenmalige link! Opnieuw onderhandelen over de codering is mislukt. No comment provided by engineer. + + Encryption renegotiation in progress. + Er wordt opnieuw onderhandeld over de encryptie. + No comment provided by engineer. + Enter Passcode Voer toegangscode in @@ -2704,26 +3132,36 @@ Dit is uw eigen eenmalige link! Fout bij het afbreken van adres wijziging No comment provided by engineer. + + Error accepting conditions + Fout bij het accepteren van voorwaarden + alert title + Error accepting contact request Fout bij het accepteren van een contactverzoek No comment provided by engineer. - - Error accessing database file - Fout bij toegang tot database bestand - No comment provided by engineer. - Error adding member(s) Fout bij het toevoegen van leden No comment provided by engineer. + + Error adding server + Fout bij toevoegen server + alert title + Error changing address Fout bij wijzigen van adres No comment provided by engineer. + + Error changing connection profile + Fout bij wijzigen van verbindingsprofiel + No comment provided by engineer. + Error changing role Fout bij wisselen van rol @@ -2734,6 +3172,16 @@ Dit is uw eigen eenmalige link! Fout bij wijzigen van instelling No comment provided by engineer. + + Error changing to incognito! + Fout bij het overschakelen naar incognito! + No comment provided by engineer. + + + Error checking token status + Fout bij het controleren van de tokenstatus + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw. @@ -2754,6 +3202,11 @@ Dit is uw eigen eenmalige link! Fout bij maken van groep link No comment provided by engineer. + + Error creating list + Fout bij het aanmaken van de lijst + alert title + Error creating member contact Fout bij aanmaken contact @@ -2769,6 +3222,11 @@ Dit is uw eigen eenmalige link! Fout bij aanmaken van profiel! No comment provided by engineer. + + Error creating report + Fout bij het rapporteren + No comment provided by engineer. + Error decrypting file Fout bij het ontsleutelen van bestand @@ -2849,9 +3307,14 @@ Dit is uw eigen eenmalige link! Fout bij lid worden van groep No comment provided by engineer. - - Error loading %@ servers - Fout bij het laden van %@ servers + + Error loading servers + Fout bij het laden van servers + alert title + + + Error migrating settings + Fout bij migreren van instellingen No comment provided by engineer. @@ -2862,7 +3325,7 @@ Dit is uw eigen eenmalige link! Error receiving file Fout bij ontvangen van bestand - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3337,36 @@ Dit is uw eigen eenmalige link! Fout bij opnieuw verbinden van servers No comment provided by engineer. + + Error registering for notifications + Fout bij registreren voor meldingen + alert title + Error removing member Fout bij verwijderen van lid No comment provided by engineer. + + Error reordering lists + Fout bij het opnieuw ordenen van lijsten + alert title + Error resetting statistics Fout bij het resetten van statistieken No comment provided by engineer. - - Error saving %@ servers - Fout bij opslaan van %@ servers - No comment provided by engineer. - Error saving ICE servers Fout bij opslaan van ICE servers No comment provided by engineer. + + Error saving chat list + Fout bij het opslaan van chatlijst + alert title + Error saving group profile Fout bij opslaan van groep profiel @@ -2909,6 +3382,11 @@ Dit is uw eigen eenmalige link! Fout bij opslaan van wachtwoord in de keychain No comment provided by engineer. + + Error saving servers + Fout bij het opslaan van servers + alert title + Error saving settings Fout bij opslaan van instellingen @@ -2954,16 +3432,26 @@ Dit is uw eigen eenmalige link! Fout bij het stoppen van de chat No comment provided by engineer. + + Error switching profile + Fout bij wisselen van profiel + No comment provided by engineer. + Error switching profile! Fout bij wisselen van profiel! - No comment provided by engineer. + alertTitle Error synchronizing connection Fout bij het synchroniseren van de verbinding No comment provided by engineer. + + Error testing server connection + Fout bij het testen van de serververbinding + No comment provided by engineer. + Error updating group link Fout bij bijwerken van groep link @@ -2974,6 +3462,11 @@ Dit is uw eigen eenmalige link! Fout bij updaten van bericht No comment provided by engineer. + + Error updating server + Fout bij het updaten van de server + alert title + Error updating settings Fout bij bijwerken van instellingen @@ -3002,8 +3495,9 @@ Dit is uw eigen eenmalige link! Error: %@ Fout: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3514,11 @@ Dit is uw eigen eenmalige link! Fouten No comment provided by engineer. + + Errors in servers configuration. + Fouten in de serverconfiguratie. + servers error + Even when disabled in the conversation. Zelfs wanneer uitgeschakeld in het gesprek. @@ -3035,6 +3534,11 @@ Dit is uw eigen eenmalige link! Uitklappen chat item action + + Expired + Verlopen + token status text + Export database Database exporteren @@ -3075,20 +3579,49 @@ Dit is uw eigen eenmalige link! Snel en niet wachten tot de afzender online is! No comment provided by engineer. + + Faster deletion of groups. + Sneller verwijderen van groepen. + No comment provided by engineer. + Faster joining and more reliable messages. Snellere deelname en betrouwbaardere berichten. No comment provided by engineer. + + Faster sending messages. + Sneller verzenden van berichten. + No comment provided by engineer. + Favorite Favoriet swipe action + + Favorites + Favorieten + No comment provided by engineer. + File error Bestandsfout - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Bestandsfouten: +%@ + alert message + + + File is blocked by server operator: +%@. + Bestand is geblokkeerd door serveroperator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,9 +3678,9 @@ Dit is uw eigen eenmalige link! Bestanden en media chat feature - - Files and media are prohibited in this group. - Bestanden en media zijn verboden in deze groep. + + Files and media are prohibited. + Bestanden en media zijn niet toegestaan. No comment provided by engineer. @@ -3157,7 +3690,7 @@ Dit is uw eigen eenmalige link! Files and media prohibited! - Bestanden en media verboden! + Bestanden en media niet toegestaan! No comment provided by engineer. @@ -3182,7 +3715,7 @@ Dit is uw eigen eenmalige link! Find chats faster - Vind gesprekken sneller + Vind chats sneller No comment provided by engineer. @@ -3215,21 +3748,71 @@ Dit is uw eigen eenmalige link! Herstel wordt niet ondersteund door groepslid No comment provided by engineer. + + For all moderators + Voor alle moderators + No comment provided by engineer. + + + For chat profile %@: + Voor chatprofiel %@: + servers error + For console Voor console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Als uw contactpersoon bijvoorbeeld berichten ontvangt via een SimpleX Chat-server, worden deze door uw app via een Flux-server verzonden. + No comment provided by engineer. + + + For me + Voor mij + No comment provided by engineer. + + + For private routing + Voor privé-routering + No comment provided by engineer. + + + For social media + Voor social media + No comment provided by engineer. + Forward Doorsturen chat item action + + Forward %d message(s)? + %d bericht(en) doorsturen? + alert title + Forward and save messages Berichten doorsturen en opslaan No comment provided by engineer. + + Forward messages + Berichten doorsturen + alert action + + + Forward messages without files? + Berichten doorsturen zonder bestanden? + alert message + + + Forward up to 20 messages at once. + Stuur maximaal 20 berichten tegelijk door. + No comment provided by engineer. + Forwarded Doorgestuurd @@ -3240,6 +3823,11 @@ Dit is uw eigen eenmalige link! Doorgestuurd vanuit No comment provided by engineer. + + Forwarding %lld messages + %lld berichten doorsturen + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. De doorstuurserver %@ kon geen verbinding maken met de bestemmingsserver %@. Probeer het later opnieuw. @@ -3289,11 +3877,6 @@ Fout: %2$@ Volledige naam (optioneel) No comment provided by engineer. - - Full name: - Volledige naam: - No comment provided by engineer. - Fully decentralized – visible only to members. Volledig gedecentraliseerd – alleen zichtbaar voor leden. @@ -3314,6 +3897,11 @@ Fout: %2$@ GIF's en stickers No comment provided by engineer. + + Get notified when mentioned. + Ontvang een melding als u vermeld wordt. + No comment provided by engineer. + Good afternoon! Goedemiddag! @@ -3379,41 +3967,6 @@ Fout: %2$@ Groep links No comment provided by engineer. - - Group members can add message reactions. - Groepsleden kunnen bericht reacties toevoegen. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) - No comment provided by engineer. - - - Group members can send SimpleX links. - Groepsleden kunnen SimpleX-links verzenden. - No comment provided by engineer. - - - Group members can send direct messages. - Groepsleden kunnen directe berichten sturen. - No comment provided by engineer. - - - Group members can send disappearing messages. - Groepsleden kunnen verdwijnende berichten sturen. - No comment provided by engineer. - - - Group members can send files and media. - Groepsleden kunnen bestanden en media verzenden. - No comment provided by engineer. - - - Group members can send voice messages. - Groepsleden kunnen spraak berichten verzenden. - No comment provided by engineer. - Group message: Groep bericht: @@ -3454,11 +4007,21 @@ Fout: %2$@ De groep wordt voor u verwijderd, dit kan niet ongedaan worden gemaakt! No comment provided by engineer. + + Groups + Groepen + No comment provided by engineer. + Help Help No comment provided by engineer. + + Help admins moderating their groups. + Help beheerders bij het modereren van hun groepen. + No comment provided by engineer. + Hidden Verborgen @@ -3466,7 +4029,7 @@ Fout: %2$@ Hidden chat profiles - Verborgen chat profielen + Verborgen chatprofielen No comment provided by engineer. @@ -3509,10 +4072,20 @@ Fout: %2$@ Hoe SimpleX werkt No comment provided by engineer. + + How it affects privacy + Hoe het de privacy beïnvloedt + No comment provided by engineer. + + + How it helps privacy + Hoe het de privacy helpt + No comment provided by engineer. + How it works Hoe het werkt - No comment provided by engineer. + alert button How to @@ -3539,6 +4112,11 @@ Fout: %2$@ ICE servers (één per lijn) No comment provided by engineer. + + IP address + IP-adres + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Als je elkaar niet persoonlijk kunt ontmoeten, laat dan de QR-code zien in een videogesprek of deel de link. @@ -3546,7 +4124,7 @@ Fout: %2$@ If you enter this passcode when opening the app, all app data will be irreversibly removed! - Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens onomkeerbaar verwijderd! + Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens definitief verwijderd! No comment provided by engineer. @@ -3579,8 +4157,8 @@ Fout: %2$@ Onmiddellijk No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immuun voor spam en misbruik No comment provided by engineer. @@ -3614,6 +4192,13 @@ Fout: %2$@ Archief importeren No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Verbeterde levering, minder data gebruik. +Binnenkort meer verbeteringen! + No comment provided by engineer. + Improved message delivery Verbeterde berichtbezorging @@ -3644,6 +4229,16 @@ Fout: %2$@ Geluiden tijdens het bellen No comment provided by engineer. + + Inappropriate content + Ongepaste inhoud + report reason + + + Inappropriate profile + Ongepast profiel + report reason + Incognito Incognito @@ -3714,6 +4309,11 @@ Fout: %2$@ Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Direct + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4321,6 @@ Fout: %2$@ No comment provided by engineer. - - Instantly - Direct - No comment provided by engineer. - Interface Interface @@ -3736,6 +4331,31 @@ Fout: %2$@ Interface kleuren No comment provided by engineer. + + Invalid + Ongeldig + token status text + + + Invalid (bad token) + Ongeldig (ongeldig token) + token status text + + + Invalid (expired) + Ongeldig (verlopen) + token status text + + + Invalid (unregistered) + Ongeldig (niet geregistreerd) + token status text + + + Invalid (wrong topic) + Ongeldig (verkeerd onderwerp) + token status text + Invalid QR code Ongeldige QR-code @@ -3774,7 +4394,7 @@ Fout: %2$@ Invalid server address! Ongeldig server adres! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4416,11 @@ Fout: %2$@ Nodig leden uit No comment provided by engineer. + + Invite to chat + Uitnodigen voor een chat + No comment provided by engineer. + Invite to group Uitnodigen voor groep @@ -3808,17 +4433,17 @@ Fout: %2$@ Irreversible message deletion is prohibited in this chat. - Het onomkeerbaar verwijderen van berichten is verboden in dit gesprek. + Het definitief verwijderen van berichten is niet toegestaan in dit gesprek. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. - Het onomkeerbaar verwijderen van berichten is verboden in deze groep. + + Irreversible message deletion is prohibited. + Het definitief verwijderen van berichten is verbHet definitief verwijderen van berichten is niet toegestaan.. No comment provided by engineer. It allows having many anonymous connections without any shared data between them in a single chat profile. - Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel. + Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel. No comment provided by engineer. @@ -3859,7 +4484,7 @@ Fout: %2$@ Join - Word lid van + Word lid swipe action @@ -3902,7 +4527,7 @@ Dit is jouw link voor groep %@! Keep Bewaar - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4542,7 @@ Dit is jouw link voor groep %@! Keep unused invitation? Ongebruikte uitnodiging bewaren? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4579,16 @@ Dit is jouw link voor groep %@! Verlaten swipe action + + Leave chat + Chat verlaten + No comment provided by engineer. + + + Leave chat? + Chat verlaten? + No comment provided by engineer. + Leave group Groep verlaten @@ -3994,6 +4629,21 @@ Dit is jouw link voor groep %@! Gelinkte desktops No comment provided by engineer. + + List + Lijst + swipe action + + + List name and emoji should be different for all lists. + De naam en emoji van de lijst moeten voor alle lijsten verschillend zijn. + No comment provided by engineer. + + + List name... + Naam van lijst... + No comment provided by engineer. + Live message! Live bericht! @@ -4004,11 +4654,6 @@ Dit is jouw link voor groep %@! Live berichten No comment provided by engineer. - - Local - Lokaal - No comment provided by engineer. - Local name Lokale naam @@ -4029,11 +4674,6 @@ Dit is jouw link voor groep %@! Vergrendeling modus No comment provided by engineer. - - Make a private connection - Maak een privéverbinding - No comment provided by engineer. - Make one message disappear Eén bericht laten verdwijnen @@ -4044,21 +4684,11 @@ Dit is jouw link voor groep %@! Profiel privé maken! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Zorg ervoor dat %@ server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Zorg ervoor dat WebRTC ICE server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Veel mensen vroegen: *als SimpleX geen gebruikers-ID's heeft, hoe kan het dan berichten bezorgen?* - No comment provided by engineer. - Mark deleted for everyone Markeer verwijderd voor iedereen @@ -4104,6 +4734,16 @@ Dit is jouw link voor groep %@! Lid inactief item status text + + Member reports + Ledenrapporten + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + De rol van het lid wordt gewijzigd naar "%@". Alle chatleden worden op de hoogte gebracht. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. De rol van lid wordt gewijzigd in "%@". Alle groepsleden worden op de hoogte gebracht. @@ -4114,11 +4754,61 @@ Dit is jouw link voor groep %@! De rol van lid wordt gewijzigd in "%@". Het lid ontvangt een nieuwe uitnodiging. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt! No comment provided by engineer. + + Members can add message reactions. + Groepsleden kunnen bericht reacties toevoegen. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) + No comment provided by engineer. + + + Members can report messsages to moderators. + Leden kunnen berichten melden bij moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Groepsleden kunnen SimpleX-links verzenden. + No comment provided by engineer. + + + Members can send direct messages. + Groepsleden kunnen directe berichten sturen. + No comment provided by engineer. + + + Members can send disappearing messages. + Groepsleden kunnen verdwijnende berichten sturen. + No comment provided by engineer. + + + Members can send files and media. + Groepsleden kunnen bestanden en media verzenden. + No comment provided by engineer. + + + Members can send voice messages. + Groepsleden kunnen spraak berichten verzenden. + No comment provided by engineer. + + + Mention members 👋 + Vermeld leden 👋 + No comment provided by engineer. + Menus Menu's @@ -4166,12 +4856,12 @@ Dit is jouw link voor groep %@! Message reactions are prohibited in this chat. - Reacties op berichten zijn verboden in deze chat. + Reacties op berichten zijn niet toegestaan in deze chat. No comment provided by engineer. - - Message reactions are prohibited in this group. - Reacties op berichten zijn verboden in deze groep. + + Message reactions are prohibited. + Reacties op berichten zijn niet toegestaan. No comment provided by engineer. @@ -4184,6 +4874,11 @@ Dit is jouw link voor groep %@! Berichtservers No comment provided by engineer. + + Message shape + Berichtvorm + No comment provided by engineer. + Message source remains private. Berichtbron blijft privé. @@ -4224,6 +4919,11 @@ Dit is jouw link voor groep %@! Berichten van %@ worden getoond! No comment provided by engineer. + + Messages in this chat will never be deleted. + Berichten in deze chat zullen nooit worden verwijderd. + alert message + Messages received Berichten ontvangen @@ -4234,6 +4934,11 @@ Dit is jouw link voor groep %@! Berichten verzonden No comment provided by engineer. + + Messages were deleted after you selected them. + Berichten zijn verwijderd nadat u ze had geselecteerd. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. @@ -4299,9 +5004,9 @@ Dit is jouw link voor groep %@! Migratie is voltooid No comment provided by engineer. - - Migrations: %@ - Migraties: %@ + + Migrations: + Migraties: No comment provided by engineer. @@ -4319,6 +5024,11 @@ Dit is jouw link voor groep %@! Gemodereerd op: %@ copied message info + + More + Meer + swipe action + More improvements are coming soon! Meer verbeteringen volgen snel! @@ -4329,6 +5039,11 @@ Dit is jouw link voor groep %@! Betrouwbaardere netwerkverbinding. No comment provided by engineer. + + More reliable notifications + Betrouwbaardere meldingen + No comment provided by engineer. + Most likely this connection is deleted. Hoogstwaarschijnlijk is deze verbinding verwijderd. @@ -4336,13 +5051,18 @@ Dit is jouw link voor groep %@! Multiple chat profiles - Meerdere chat profielen + Meerdere chatprofielen No comment provided by engineer. Mute Dempen - swipe action + notification label action + + + Mute all + Alles dempen + notification label action Muted when inactive! @@ -4364,6 +5084,11 @@ Dit is jouw link voor groep %@! Netwerkverbinding No comment provided by engineer. + + Network decentralization + Netwerk decentralisatie + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden. @@ -4374,6 +5099,11 @@ Dit is jouw link voor groep %@! Netwerkbeheer No comment provided by engineer. + + Network operator + Netwerkbeheerder + No comment provided by engineer. + Network settings Netwerk instellingen @@ -4384,11 +5114,26 @@ Dit is jouw link voor groep %@! Netwerk status No comment provided by engineer. + + New + Nieuw + token status text + New Passcode Nieuwe toegangscode No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Elke keer dat u de app start, worden er nieuwe SOCKS-inloggegevens gebruikt. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Voor elke server worden nieuwe SOCKS-inloggegevens gebruikt. + No comment provided by engineer. + New chat Nieuw gesprek @@ -4409,11 +5154,6 @@ Dit is jouw link voor groep %@! Nieuw contact: notification - - New database archive - Nieuw database archief - No comment provided by engineer. - New desktop app! Nieuwe desktop app! @@ -4424,6 +5164,11 @@ Dit is jouw link voor groep %@! Nieuwe weergavenaam No comment provided by engineer. + + New events + Nieuwe gebeurtenissen + notification + New in %@ Nieuw in %@ @@ -4449,6 +5194,11 @@ Dit is jouw link voor groep %@! Nieuw wachtwoord… No comment provided by engineer. + + New server + Nieuwe server + No comment provided by engineer. + No Nee @@ -4459,6 +5209,21 @@ Dit is jouw link voor groep %@! Geen app wachtwoord Authentication unavailable + + No chats + Geen chats + No comment provided by engineer. + + + No chats found + Geen chats gevonden + No comment provided by engineer. + + + No chats in list %@ + Geen chats in lijst %@ + No comment provided by engineer. + No contacts selected Geen contacten geselecteerd @@ -4486,7 +5251,7 @@ Dit is jouw link voor groep %@! No filtered chats - Geen gefilterde gesprekken + Geen gefilterde chats No comment provided by engineer. @@ -4504,31 +5269,106 @@ Dit is jouw link voor groep %@! Geen info, probeer opnieuw te laden No comment provided by engineer. + + No media & file servers. + Geen media- en bestandsservers. + servers error + + + No message + Geen bericht + No comment provided by engineer. + + + No message servers. + Geen berichtenservers. + servers error + No network connection Geen netwerkverbinding No comment provided by engineer. + + No permission to record speech + Geen toestemming om spraak op te nemen + No comment provided by engineer. + + + No permission to record video + Geen toestemming om video op te nemen + No comment provided by engineer. + No permission to record voice message Geen toestemming om spraakbericht op te nemen No comment provided by engineer. + + No push server + Lokaal + No comment provided by engineer. + No received or sent files Geen ontvangen of verzonden bestanden No comment provided by engineer. + + No servers for private message routing. + Geen servers voor het routeren van privéberichten. + servers error + + + No servers to receive files. + Geen servers om bestanden te ontvangen. + servers error + + + No servers to receive messages. + Geen servers om berichten te ontvangen. + servers error + + + No servers to send files. + Geen servers om bestanden te verzenden. + servers error + + + No token! + Geen token! + alert title + + + 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! No comment provided by engineer. + + Notes + Notities + No comment provided by engineer. + Nothing selected Niets geselecteerd No comment provided by engineer. + + Nothing to forward! + Niets om door te sturen! + alert title + Notifications Meldingen @@ -4539,6 +5379,21 @@ Dit is jouw link voor groep %@! Meldingen zijn uitgeschakeld! No comment provided by engineer. + + Notifications error + Meldingsfout + alert title + + + Notifications privacy + Privacy van meldingen + No comment provided by engineer. + + + Notifications status + Meldingsstatus + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5416,13 @@ Dit is jouw link voor groep %@! Ok OK - No comment provided by engineer. + alert button Old database Oude database No comment provided by engineer. - - Old database archive - Oud database archief - No comment provided by engineer. - One-time invitation link Eenmalige uitnodiging link @@ -4597,8 +5447,13 @@ Vereist het inschakelen van VPN. Onion hosts worden niet gebruikt. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Alleen chateigenaren kunnen voorkeuren wijzigen. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**. No comment provided by engineer. @@ -4622,6 +5477,16 @@ Vereist het inschakelen van VPN. Alleen groep eigenaren kunnen spraak berichten inschakelen. No comment provided by engineer. + + Only sender and moderators see it + Alleen de verzender en moderators zien het + No comment provided by engineer. + + + Only you and moderators see it + Alleen jij en moderators zien het + No comment provided by engineer. + Only you can add message reactions. Alleen jij kunt bericht reacties toevoegen. @@ -4629,7 +5494,7 @@ Vereist het inschakelen van VPN. Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) - Alleen jij kunt berichten onomkeerbaar verwijderen (je contact kan ze markeren voor verwijdering). (24 uur) + Alleen jij kunt berichten definitief verwijderen (je contact kan ze markeren voor verwijdering). (24 uur) No comment provided by engineer. @@ -4675,13 +5540,18 @@ Vereist het inschakelen van VPN. Open Open - No comment provided by engineer. + alert action Open Settings Open instellingen No comment provided by engineer. + + Open changes + Wijzigingen openen + No comment provided by engineer. + Open chat Chat openen @@ -4692,36 +5562,45 @@ Vereist het inschakelen van VPN. Chat console openen authentication reason + + Open conditions + Open voorwaarden + No comment provided by engineer. + Open group Open groep No comment provided by engineer. + + Open link? + alert title + Open migration to another device Open de migratie naar een ander apparaat authentication reason - - Open server settings - Server instellingen openen - No comment provided by engineer. - - - Open user profiles - Gebruikers profielen openen - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Open-source protocol en code. Iedereen kan de servers draaien. - No comment provided by engineer. - Opening app… App openen… No comment provided by engineer. + + Operator + Operator + No comment provided by engineer. + + + Operator server + Operatorserver + alert title + + + Or import archive file + Of importeer archiefbestand + No comment provided by engineer. + Or paste archive link Of plak de archief link @@ -4742,15 +5621,27 @@ Vereist het inschakelen van VPN. Of laat deze code zien No comment provided by engineer. + + Or to share privately + Of om privé te delen + No comment provided by engineer. + + + Organize chats into lists + Organiseer chats in lijsten + No comment provided by engineer. + Other Ander No comment provided by engineer. - - Other %@ servers - Andere %@ servers - No comment provided by engineer. + + Other file errors: +%@ + Andere bestandsfouten: +%@ + alert message PING count @@ -4787,6 +5678,11 @@ Vereist het inschakelen van VPN. Toegangscode ingesteld! No comment provided by engineer. + + Password + Wachtwoord + No comment provided by engineer. + Password to show Wachtwoord om weer te geven @@ -4822,13 +5718,8 @@ Vereist het inschakelen van VPN. in behandeling No comment provided by engineer. - - People can connect to you only via the links you share. - Mensen kunnen alleen verbinding met u maken via de links die u deelt. - No comment provided by engineer. - - - Periodically + + Periodic Periodiek No comment provided by engineer. @@ -4844,7 +5735,7 @@ Vereist het inschakelen van VPN. Play from the chat list. - Afspelen via de gesprekken lijst. + Afspelen via de chat lijst. No comment provided by engineer. @@ -4923,7 +5814,7 @@ Fout: %@ Please store passphrase securely, you will NOT be able to access chat if you lose it. - Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken. + Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats. No comment provided by engineer. @@ -4931,11 +5822,31 @@ Fout: %@ Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u het kwijtraakt. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Probeer meldingen uit en weer in te schakelen. + token info + + + Please wait for token activation to complete. + Wacht tot de tokenactivering voltooid is. + token info + + + Please wait for token to be registered. + Wacht tot het token is geregistreerd. + token info + Polish interface Poolse interface No comment provided by engineer. + + Port + Poort + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Mogelijk is de certificaat vingerafdruk in het server adres onjuist @@ -4946,16 +5857,16 @@ Fout: %@ Bewaar het laatste berichtconcept, met bijlagen. No comment provided by engineer. - - Preset server - Vooraf ingestelde server - No comment provided by engineer. - Preset server address Vooraf ingesteld server adres No comment provided by engineer. + + Preset servers + Vooraf ingestelde servers + No comment provided by engineer. + Preview Voorbeeld @@ -4971,16 +5882,36 @@ Fout: %@ Privacy en beveiliging No comment provided by engineer. + + Privacy for your customers. + Privacy voor uw klanten. + 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 No comment provided by engineer. + + Private media file names. + Namen van persoonlijke mediabestanden. + No comment provided by engineer. + Private message routing Routering van privéberichten @@ -5021,16 +5952,6 @@ Fout: %@ Profiel afbeeldingen No comment provided by engineer. - - Profile name - Profielnaam - No comment provided by engineer. - - - Profile name: - Profielnaam: - No comment provided by engineer. - Profile password Profiel wachtwoord @@ -5044,7 +5965,7 @@ Fout: %@ Profile update will be sent to your contacts. Profiel update wordt naar uw contacten verzonden. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5053,7 +5974,7 @@ Fout: %@ Prohibit irreversible message deletion. - Verbied het onomkeerbaar verwijderen van berichten. + Verbied het definitief verwijderen van berichten. No comment provided by engineer. @@ -5066,6 +5987,11 @@ Fout: %@ Berichten reacties verbieden. No comment provided by engineer. + + Prohibit reporting messages to moderators. + Het melden van berichten aan moderators is niet toegestaan. + No comment provided by engineer. + Prohibit sending SimpleX links. Verbied het verzenden van SimpleX-links @@ -5110,7 +6036,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Protect your chat profiles with a password! - Bescherm je chat profielen met een wachtwoord! + Bescherm je chatprofielen met een wachtwoord! No comment provided by engineer. @@ -5133,6 +6059,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxied servers No comment provided by engineer. + + Proxy requires password + Proxy vereist wachtwoord + No comment provided by engineer. + Push notifications Push meldingen @@ -5173,26 +6104,21 @@ 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/app-settings.html#your-simplex-contact-address). - 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/chat-profiles.html#incognito-mode). Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/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. - Lees meer in onze GitHub repository. - 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). @@ -5323,11 +6249,26 @@ Schakel dit in in *Netwerk en servers*-instellingen. Verminderd batterijgebruik No comment provided by engineer. + + Register + Register + No comment provided by engineer. + + + Register notification token? + Meldingstoken registreren? + token info + + + Registered + Geregistreerd + token status text + Reject Afwijzen reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6295,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Verwijderen No comment provided by engineer. + + Remove archive? + Archief verwijderen? + No comment provided by engineer. + Remove image Verwijder afbeelding @@ -5419,6 +6365,56 @@ Schakel dit in in *Netwerk en servers*-instellingen. Antwoord chat item action + + Report + rapporteren + chat item action + + + Report content: only group moderators will see it. + Inhoud melden: alleen groepsmoderators kunnen dit zien. + report reason + + + Report member profile: only group moderators will see it. + Rapporteer ledenprofiel: alleen groepsmoderators kunnen dit zien. + report reason + + + Report other: only group moderators will see it. + Anders melden: alleen groepsmoderators kunnen het zien. + report reason + + + Report reason? + Reden melding? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Spam melden: alleen groepsmoderators kunnen het zien. + report reason + + + Report violation: only group moderators will see it. + Rapporteer overtreding: alleen groepsmoderators kunnen dit zien. + report reason + + + Report: %@ + rapporteer: %@ + report in notification + + + Reporting messages to moderators is prohibited. + Het is niet toegestaan om berichten aan moderators te melden. + No comment provided by engineer. + + + Reports + Rapporten + No comment provided by engineer. + Required Vereist @@ -5466,7 +6462,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Restart the app to create a new chat profile - Start de app opnieuw om een nieuw chat profiel aan te maken + Start de app opnieuw om een nieuw chatprofiel aan te maken No comment provided by engineer. @@ -5504,6 +6500,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Onthullen chat item action + + Review conditions + Voorwaarden bekijken + No comment provided by engineer. + Revoke Intrekken @@ -5534,6 +6535,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. SMP server No comment provided by engineer. + + SOCKS proxy + SOCKS proxy + No comment provided by engineer. + Safely receive files Veilig bestanden ontvangen @@ -5547,17 +6553,18 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save Opslaan - chat item action + alert button +chat item action Save (and notify contacts) Bewaar (en informeer contacten) - No comment provided by engineer. + alert button Save and notify contact Opslaan en Contact melden - No comment provided by engineer. + alert button Save and notify group members @@ -5574,24 +6581,19 @@ Schakel dit in in *Netwerk en servers*-instellingen. Groep profiel opslaan en bijwerken No comment provided by engineer. - - Save archive - Bewaar archief - No comment provided by engineer. - - - Save auto-accept settings - Sla instellingen voor automatisch accepteren op - No comment provided by engineer. - Save group profile Groep profiel opslaan No comment provided by engineer. + + Save list + Lijst opslaan + No comment provided by engineer. + Save passphrase and open chat - Bewaar het wachtwoord en open je gesprekken + Wachtwoord opslaan en open je chats No comment provided by engineer. @@ -5602,7 +6604,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save preferences? Voorkeuren opslaan? - No comment provided by engineer. + alert title Save profile password @@ -5611,24 +6613,24 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save servers - Bewaar servers + Servers opslaan No comment provided by engineer. Save servers? Servers opslaan? - No comment provided by engineer. - - - Save settings? - Instellingen opslaan? - No comment provided by engineer. + alert title Save welcome message? Welkom bericht opslaan? No comment provided by engineer. + + Save your profile? + Uw profiel opslaan? + alert title + Saved Opgeslagen @@ -5649,6 +6651,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Opgeslagen bericht message info title + + Saving %lld messages + %lld berichten opslaan + No comment provided by engineer. + Scale Schaal @@ -5729,6 +6736,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Selecteer chat item action + + Select chat profile + Selecteer chatprofiel + No comment provided by engineer. + Selected %lld %lld geselecteerd @@ -5819,9 +6831,9 @@ Schakel dit in in *Netwerk en servers*-instellingen. Meldingen verzenden No comment provided by engineer. - - Send notifications: - Meldingen verzenden: + + Send private reports + Rapporteer privé No comment provided by engineer. @@ -5847,7 +6859,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Sender cancelled file transfer. Afzender heeft bestandsoverdracht geannuleerd. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6956,16 @@ Schakel dit in in *Netwerk en servers*-instellingen. Verzonden via proxy No comment provided by engineer. + + Server + Server + No comment provided by engineer. + + + Server added to operator %@. + Server toegevoegd aan operator %@. + alert message + Server address Server adres @@ -5959,6 +6981,21 @@ Schakel dit in in *Netwerk en servers*-instellingen. Serveradres is incompatibel met netwerkinstellingen: %@. No comment provided by engineer. + + Server operator changed. + Serveroperator gewijzigd. + alert title + + + Server operators + Serverbeheerders + No comment provided by engineer. + + + Server protocol changed. + Serverprotocol gewijzigd. + alert title + Server requires authorization to create queues, check password Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord @@ -6014,6 +7051,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Stel 1 dag in No comment provided by engineer. + + Set chat name… + Stel chatnaam in… + No comment provided by engineer. + Set contact name… Contactnaam instellen… @@ -6034,6 +7076,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Stel het in in plaats van systeemverificatie. No comment provided by engineer. + + Set message expiration in chats. + Stel de berichtvervaldatum in chats in. + No comment provided by engineer. + Set passcode Toegangscode instellen @@ -6064,6 +7111,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Instellingen No comment provided by engineer. + + Settings were changed. + Instellingen zijn gewijzigd. + alert message + Shape profile images Vorm profiel afbeeldingen @@ -6072,22 +7124,38 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share Deel - chat item action + alert action +chat item action Share 1-time link Eenmalige link delen No comment provided by engineer. + + Share 1-time link with a friend + Deel eenmalig een link met een vriend + No comment provided by engineer. + + + Share SimpleX address on social media. + Deel het SimpleX-adres op sociale media. + No comment provided by engineer. + Share address Adres delen No comment provided by engineer. + + Share address publicly + Adres openbaar delen + No comment provided by engineer. + Share address with contacts? Adres delen met contacten? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7167,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Deel link No comment provided by engineer. + + Share profile + Profiel delen + No comment provided by engineer. + Share this 1-time invite link Deel deze eenmalige uitnodigingslink @@ -6114,6 +7187,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Delen met contacten No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Toon QR-code @@ -6169,6 +7246,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX adres No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + Simplex-chat en flux hebben een overeenkomst gemaakt om door flux geëxploiteerde servers in de app op te nemen. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. De beveiliging van SimpleX Chat is gecontroleerd door Trail of Bits. @@ -6199,6 +7281,20 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX adres No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + SimpleX-adressen en eenmalige links kunnen veilig worden gedeeld via elke messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX adres of eenmalige link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX contact adres @@ -6219,9 +7315,9 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX links chat feature - - SimpleX links are prohibited in this group. - SimpleX-links zijn in deze groep verboden. + + SimpleX links are prohibited. + SimpleX-links zijn niet toegestaan. No comment provided by engineer. @@ -6234,6 +7330,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Eenmalige SimpleX uitnodiging simplex link type + + SimpleX protocols reviewed by Trail of Bits. + SimpleX-protocollen beoordeeld door Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Vereenvoudigde incognitomodus @@ -6264,6 +7365,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Soft blur media + + Some app settings were not migrated. + Sommige app-instellingen zijn niet gemigreerd. + No comment provided by engineer. + Some file(s) were not exported: Sommige bestanden zijn niet geëxporteerd: @@ -6279,11 +7385,24 @@ Schakel dit in in *Netwerk en servers*-instellingen. Er zijn enkele niet-fatale fouten opgetreden tijdens het importeren: No comment provided by engineer. + + Some servers failed the test: +%@ + Sommige servers zijn niet geslaagd voor de test: +%@ + alert message + Somebody Iemand notification title + + Spam + Spam + blocking reason +report reason + Square, circle, or anything in between. Vierkant, cirkel of iets daartussenin. @@ -6329,11 +7448,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Stop de chat om database acties mogelijk te maken - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Stop de chat om de chat database te exporteren, importeren of verwijderen. U kunt geen berichten ontvangen en verzenden terwijl de chat is gestopt. @@ -6362,18 +7476,23 @@ Schakel dit in in *Netwerk en servers*-instellingen. Stop sharing Stop met delen - No comment provided by engineer. + alert action Stop sharing address? Stop met het delen van adres? - No comment provided by engineer. + alert title Stopping chat Chat stoppen No comment provided by engineer. + + Storage + Opslag + No comment provided by engineer. + Strong Krachtig @@ -6381,22 +7500,22 @@ Schakel dit in in *Netwerk en servers*-instellingen. Submit - Indienen + Bevestigen No comment provided by engineer. Subscribed - Ingeschreven + Subscribed No comment provided by engineer. Subscription errors - Inschrijving fouten + Subscription fouten No comment provided by engineer. Subscriptions ignored - Inschrijvingen genegeerd + Subscriptions genegeerd No comment provided by engineer. @@ -6404,6 +7523,16 @@ Schakel dit in in *Netwerk en servers*-instellingen. Ondersteuning van SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Wisselen tussen audio en video tijdens het gesprek. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Wijzig chatprofiel voor eenmalige uitnodigingen. + No comment provided by engineer. + System Systeem @@ -6424,6 +7553,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Timeout van TCP-verbinding No comment provided by engineer. + + TCP port for messaging + TCP-poort voor berichtenuitwisseling + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7573,21 @@ Schakel dit in in *Netwerk en servers*-instellingen. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Staart + No comment provided by engineer. + Take picture Foto nemen 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 button Tik op de knop @@ -6482,13 +7626,18 @@ Schakel dit in in *Netwerk en servers*-instellingen. Temporary file error Tijdelijke bestandsfout - No comment provided by engineer. + file error alert title Test failed at step %@. Test mislukt bij stap %@. server test failure + + Test notifications + Testmeldingen + No comment provided by engineer. + Test server Server test @@ -6502,7 +7651,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Tests failed! Testen mislukt! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7668,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Dank aan de gebruikers – draag bij via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Het eerste platform zonder gebruikers-ID's, privé door ontwerp. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7680,11 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + De app beschermt uw privacy door in elk gesprek andere operatoren te gebruiken. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). De app vraagt om downloads van onbekende bestandsservers (behalve .onion) te bevestigen. @@ -6551,6 +7700,11 @@ 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, your contact may be offline. + De verbinding heeft de limiet van niet-afgeleverde berichten bereikt. Uw contactpersoon is mogelijk offline. + No comment provided by engineer. + The connection you accepted will be cancelled! De door u geaccepteerde verbinding wordt geannuleerd! @@ -6571,6 +7725,11 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. De hash van het vorige bericht is anders. @@ -6596,19 +7755,19 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De berichten worden voor alle leden als gemodereerd gemarkeerd. No comment provided by engineer. - - The next generation of private messaging - De volgende generatie privéberichten - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd. No comment provided by engineer. - - The profile is only shared with your contacts. - Het profiel wordt alleen gedeeld met uw contacten. + + The same conditions will apply to operator **%@**. + Dezelfde voorwaarden gelden voor operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + De tweede vooraf ingestelde operator in de app! No comment provided by engineer. @@ -6623,7 +7782,12 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The servers for new connections of your current chat profile **%@**. - De servers voor nieuwe verbindingen van uw huidige chat profiel **%@**. + De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**. + No comment provided by engineer. + + + The servers for new files of your current chat profile **%@**. + De servers voor nieuwe bestanden van uw huidige chatprofiel **%@**. No comment provided by engineer. @@ -6631,11 +7795,21 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De tekst die u hebt geplakt is geen SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + Het geüploade databasearchief wordt permanent van de servers verwijderd. + No comment provided by engineer. + Themes Thema's No comment provided by engineer. + + These conditions will also apply for: **%@**. + Deze voorwaarden zijn ook van toepassing op: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Deze instellingen zijn voor uw huidige profiel **%@**. @@ -6656,9 +7830,14 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Deze actie kan niet ongedaan worden gemaakt, de berichten die eerder zijn verzonden en ontvangen dan geselecteerd, worden verwijderd. Het kan enkele minuten duren. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Deze actie kan niet ongedaan worden gemaakt. De berichten die eerder in deze chat zijn verzonden en ontvangen dan geselecteerd, worden verwijderd. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. + Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan definitief verloren. No comment provided by engineer. @@ -6673,7 +7852,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. This device name - Deze apparaatnaam + Naam van dit apparaat No comment provided by engineer. @@ -6701,14 +7880,23 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Dit is uw eigen eenmalige link! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Deze link is gebruikt met een ander mobiel apparaat. Maak een nieuwe link op de desktop. No comment provided by engineer. + + This message was deleted or not received yet. + Dit bericht is verwijderd of nog niet ontvangen. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. - Deze instelling is van toepassing op berichten in je huidige chat profiel **%@**. + Deze instelling is van toepassing op berichten in je huidige chatprofiel **%@**. No comment provided by engineer. @@ -6736,9 +7924,9 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Om een nieuwe verbinding te maken No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten. + + To protect against your link being replaced, you can compare contact security codes. + Om te voorkomen dat uw link wordt vervangen, kunt u contactbeveiligingscodes vergelijken. No comment provided by engineer. @@ -6758,6 +7946,26 @@ You will be prompted to complete authentication before this feature is enabled.< U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingeschakeld. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten. + No comment provided by engineer. + + + To receive + Om te ontvangen + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Geef toestemming om de microfoon te gebruiken om spraak op te nemen. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Om video op te nemen, dient u toestemming te geven om de camera te gebruiken. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Geef toestemming om de microfoon te gebruiken om een spraakbericht op te nemen. @@ -6765,7 +7973,12 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. - Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chat profielen**. + Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**. + No comment provided by engineer. + + + To send + Om te verzenden No comment provided by engineer. @@ -6773,6 +7986,11 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + Om de servers van **%@** te gebruiken, moet u de gebruiksvoorwaarden accepteren. + 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. @@ -6788,6 +8006,11 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Schakel incognito in tijdens het verbinden. No comment provided by engineer. + + Token status: %@. + Tokenstatus: %@. + token status + Toolbar opacity De transparantie van de werkbalk @@ -6863,6 +8086,11 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Lid deblokkeren? No comment provided by engineer. + + Undelivered messages + Niet afgeleverde berichten + No comment provided by engineer. + Unexpected migration state Onverwachte migratiestatus @@ -6880,7 +8108,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Unhide chat profile - Chat profiel zichtbaar maken + Chatprofiel zichtbaar maken No comment provided by engineer. @@ -6911,7 +8139,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Unknown servers! Onbekende servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +8176,17 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Unmute Dempen opheffen - swipe action + notification label action Unread Ongelezen swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden. @@ -6980,6 +8212,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Instellingen actualiseren? No comment provided by engineer. + + Updated conditions + Bijgewerkte voorwaarden + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Door de instellingen bij te werken, wordt de client opnieuw verbonden met alle servers. @@ -7020,16 +8257,35 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Archief uploaden No comment provided by engineer. + + Use %@ + Gebruik %@ + No comment provided by engineer. + Use .onion hosts Gebruik .onion-hosts No comment provided by engineer. + + Use SOCKS proxy + Gebruik SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX Chat servers gebruiken? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + Gebruik TCP-poort %@ als er geen poort is opgegeven. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Gebruik chat @@ -7040,6 +8296,16 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik het huidige profiel No comment provided by engineer. + + Use for files + Gebruik voor bestanden + No comment provided by engineer. + + + Use for messages + Gebruik voor berichten + No comment provided by engineer. + Use for new connections Gebruik voor nieuwe verbindingen @@ -7080,6 +8346,15 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik server No comment provided by engineer. + + Use servers + Gebruik servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Gebruik de app tijdens het gesprek. @@ -7090,9 +8365,9 @@ 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. - - User profile - Gebruikers profiel + + Use web port + Gebruik een webpoort No comment provided by engineer. @@ -7100,9 +8375,14 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruikersselectie No comment provided by engineer. + + Username + Gebruikersnaam + No comment provided by engineer. + Using SimpleX Chat servers. - SimpleX Chat servers gebruiken. + Gebruik SimpleX Chat servers. No comment provided by engineer. @@ -7170,11 +8450,21 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Video's en bestanden tot 1 GB No comment provided by engineer. + + View conditions + Bekijk voorwaarden + No comment provided by engineer. + View security code Beveiligingscode bekijken No comment provided by engineer. + + View updated conditions + Bekijk de bijgewerkte voorwaarden + No comment provided by engineer. + Visible history Zichtbare geschiedenis @@ -7187,12 +8477,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Voice messages are prohibited in this chat. - Spraak berichten zijn verboden in deze chat. + Spraak berichten zijn niet toegestaan in dit gesprek. No comment provided by engineer. - - Voice messages are prohibited in this group. - Spraak berichten zijn verboden in deze groep. + + Voice messages are prohibited. + Spraak berichten zijn niet toegestaan. No comment provided by engineer. @@ -7202,7 +8492,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Voice messages prohibited! - Spraak berichten verboden! + Spraak berichten niet toegestaan! No comment provided by engineer. @@ -7285,9 +8575,9 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Bij het verbinden van audio- en video-oproepen. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Wanneer mensen vragen om verbinding te maken, kunt u dit accepteren of weigeren. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Wanneer er meer dan één operator is ingeschakeld, beschikt geen enkele operator over metagegevens om te achterhalen wie met wie communiceert. No comment provided by engineer. @@ -7333,7 +8623,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8650,6 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak XFTP server No comment provided by engineer. - - You - Jij - No comment provided by engineer. - You **must not** use the same database on two devices. U **mag** niet dezelfde database op twee apparaten gebruiken. @@ -7382,7 +8667,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You already have a chat profile with the same display name. Please choose another name. - Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam. + Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam. No comment provided by engineer. @@ -7390,6 +8675,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak U bent al verbonden met %@. No comment provided by engineer. + + You are already connected with %@. + U bent al verbonden met %@. + No comment provided by engineer. + You are already connecting to %@. U maakt al verbinding met %@. @@ -7452,6 +8742,11 @@ Deelnameverzoek herhalen? U kunt dit wijzigen in de instellingen onder uiterlijk. No comment provided by engineer. + + You can configure servers via settings. + U kunt servers configureren via instellingen. + No comment provided by engineer. + You can create it later U kan het later maken @@ -7492,6 +8787,11 @@ Deelnameverzoek herhalen? U kunt berichten naar %@ sturen vanuit gearchiveerde contacten. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + U kunt een verbindingsnaam instellen, zodat u kunt onthouden met wie de link is gedeeld. + No comment provided by engineer. + You can set lock screen notification preview via settings. U kunt een voorbeeld van een melding op het vergrendeld scherm instellen via instellingen. @@ -7507,11 +8807,6 @@ Deelnameverzoek herhalen? U kunt dit adres delen met uw contacten om hen verbinding te laten maken met **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten @@ -7535,23 +8830,23 @@ Deelnameverzoek herhalen? You can view invitation link again in connection details. U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails. - No comment provided by engineer. + alert message You can't send messages! Je kunt geen berichten versturen! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - U bepaalt via welke server(s) de berichten **ontvangen**, uw contacten de servers die u gebruikt om ze berichten te sturen. - 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. + No comment provided by engineer. + You have already requested connection via this address! U heeft al een verbinding aangevraagd via dit adres! @@ -7619,6 +8914,11 @@ Verbindingsverzoek herhalen? Je hebt een groep uitnodiging verzonden No comment provided by engineer. + + You should receive notifications. + U zou meldingen moeten ontvangen. + token info + You will be connected to group when the group host's device is online, please wait or check later! Je wordt verbonden met de groep wanneer het apparaat van de groep host online is, even geduld a.u.b. of controleer het later! @@ -7654,6 +8954,11 @@ Verbindingsverzoek herhalen? U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + U ontvangt geen berichten meer van deze chat. De chatgeschiedenis blijft bewaard. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Je ontvangt geen berichten meer van deze groep. Je gesprek geschiedenis blijft behouden. @@ -7674,31 +8979,16 @@ Verbindingsverzoek herhalen? Je gebruikt een incognito profiel voor deze groep. Om te voorkomen dat je je hoofdprofiel deelt, is het niet toegestaan om contacten uit te nodigen No comment provided by engineer. - - Your %@ servers - Uw %@ servers - No comment provided by engineer. - Your ICE servers Uw ICE servers No comment provided by engineer. - - Your SMP servers - Uw SMP servers - No comment provided by engineer. - Your SimpleX address Uw SimpleX adres No comment provided by engineer. - - Your XFTP servers - Uw XFTP servers - No comment provided by engineer. - Your calls Uw oproepen @@ -7714,11 +9004,21 @@ Verbindingsverzoek herhalen? Uw chat database is niet versleuteld, stel een wachtwoord in om deze te versleutelen. No comment provided by engineer. + + Your chat preferences + Uw chat voorkeuren + alert title + Your chat profiles Uw chat profielen No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@). @@ -7734,6 +9034,11 @@ Verbindingsverzoek herhalen? Uw contacten blijven verbonden. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Uw inloggegevens worden mogelijk niet-versleuteld verzonden. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Uw huidige chat database wordt VERWIJDERD en VERVANGEN door de geïmporteerde. @@ -7764,33 +9069,36 @@ Verbindingsverzoek herhalen? Uw profiel **%@** wordt gedeeld. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. -SimpleX servers kunnen uw profiel niet zien. + + Your profile is stored on your device and only shared with your contacts. + Het profiel wordt alleen gedeeld met uw contacten. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. + alert message + Your random profile Je willekeurige profiel No comment provided by engineer. - - Your server - Uw server - No comment provided by engineer. - Your server address Uw server adres No comment provided by engineer. + + Your servers + Uw servers + No comment provided by engineer. + Your settings Uw instellingen @@ -7831,6 +9139,11 @@ SimpleX servers kunnen uw profiel niet zien. geaccepteerde oproep call status + + accepted invitation + geaccepteerde uitnodiging + chat list item title + admin Beheerder @@ -7866,6 +9179,11 @@ SimpleX servers kunnen uw profiel niet zien. en %lld andere gebeurtenissen No comment provided by engineer. + + archived report + gearchiveerd rapport + No comment provided by engineer. + attempts pogingen @@ -7898,13 +9216,14 @@ SimpleX servers kunnen uw profiel niet zien. blocked %@ - geblokkeerd %@ + blokkeerde %@ rcv group event chat item blocked by admin geblokkeerd door beheerder - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9338,7 @@ SimpleX servers kunnen uw profiel niet zien. connecting… Verbinden… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9393,8 @@ SimpleX servers kunnen uw profiel niet zien. default (%@) standaard (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9521,6 @@ SimpleX servers kunnen uw profiel niet zien. fout No comment provided by engineer. - - event happened - gebeurtenis gebeurd - No comment provided by engineer. - expired verlopen @@ -8376,20 +9691,20 @@ SimpleX servers kunnen uw profiel niet zien. gemodereerd door %@ marked deleted chat item preview text + + moderator + moderator + member role + months maanden time unit - - mute - dempen - No comment provided by engineer. - never nooit - No comment provided by engineer. + delete after time new message @@ -8420,8 +9735,8 @@ SimpleX servers kunnen uw profiel niet zien. off uit enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9778,16 @@ SimpleX servers kunnen uw profiel niet zien. peer-to-peer No comment provided by engineer. + + pending + In behandeling + No comment provided by engineer. + + + pending approval + in afwachting van goedkeuring + No comment provided by engineer. + quantum resistant e2e encryption quantum bestendige e2e-codering @@ -8478,6 +9803,11 @@ SimpleX servers kunnen uw profiel niet zien. bevestiging ontvangen… No comment provided by engineer. + + rejected + afgewezen + No comment provided by engineer. + rejected call geweigerde oproep @@ -8508,6 +9838,11 @@ SimpleX servers kunnen uw profiel niet zien. heeft je verwijderd rcv group event chat item + + requested to connect + verzocht om verbinding te maken + chat list item title + saved opgeslagen @@ -8564,7 +9899,7 @@ laatst ontvangen bericht: %2$@ set new profile picture - nieuwe profielfoto instellen + nieuwe profielfoto profile update event chat item @@ -8607,11 +9942,6 @@ laatst ontvangen bericht: %2$@ onbekende status No comment provided by engineer. - - unmute - dempen opheffen - No comment provided by engineer. - unprotected onbeschermd @@ -8709,7 +10039,7 @@ laatst ontvangen bericht: %2$@ you are observer - jij bent waarnemer + je bent waarnemer No comment provided by engineer. @@ -8739,7 +10069,7 @@ laatst ontvangen bericht: %2$@ you left - jij bent vertrokken + je bent vertrokken snd group event chat item @@ -8776,7 +10106,7 @@ laatst ontvangen bericht: %2$@
- +
@@ -8813,7 +10143,7 @@ laatst ontvangen bericht: %2$@
- +
@@ -8833,9 +10163,40 @@ laatst ontvangen bericht: %2$@
+ +
+ +
+ + + %d new events + ‐%d nieuwe gebeurtenissen + notification body + + + From %d chat(s) + notification body + + + From: %@ + Van: %@ + notification body + + + New events + Nieuwe gebeurtenissen + notification + + + New messages + Nieuwe berichten + notification + + +
- +
@@ -8857,7 +10218,7 @@ laatst ontvangen bericht: %2$@
- +
@@ -8917,7 +10278,7 @@ laatst ontvangen bericht: %2$@ Database passphrase is required to open chat. - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/contents.json b/apps/ios/SimpleX Localizations/nl.xcloc/contents.json index 4c631c367e..4b8d468de2 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/nl.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "nl", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 525d30daa6..175c8b4112 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (można skopiować) @@ -127,6 +100,16 @@ %@ jest zweryfikowany No comment provided by engineer. + + %@ server + %@ serwer + No comment provided by engineer. + + + %@ servers + %@ serwery/ów + No comment provided by engineer. + %@ uploaded %@ wgrane @@ -137,6 +120,11 @@ %@ chce się połączyć! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ i %lld członków @@ -157,11 +145,36 @@ %d dni time interval + + %d file(s) are still being downloaded. + %d plik(ów) jest dalej pobieranych. + forward confirmation reason + + + %d file(s) failed to download. + %d plik(ów) nie udało się pobrać. + forward confirmation reason + + + %d file(s) were deleted. + %d plik(ów) zostało usuniętych. + forward confirmation reason + + + %d file(s) were not downloaded. + %d plik(ów) nie zostało pobranych. + forward confirmation reason + %d hours %d godzin time interval + + %d messages not forwarded + %d wiadomości nie przekazanych + alert title + %d min %d min @@ -177,6 +190,11 @@ %d sek time interval + + %d seconds(s) + %d sekundach + delete after time + %d skipped message(s) %d pominięte wiadomość(i) @@ -247,11 +265,6 @@ %lld nowe języki interfejsu No comment provided by engineer. - - %lld second(s) - %lld sekund(y) - No comment provided by engineer. - %lld seconds %lld sekund @@ -302,11 +315,6 @@ %u pominiętych wiadomości. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nowy) @@ -317,33 +325,23 @@ (to urządzenie v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **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. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Dodaj nowy kontakt**: aby stworzyć swój jednorazowy kod QR lub link dla kontaktu. - No comment provided by engineer. - **Create group**: to create a new group. **Utwórz grupę**: aby utworzyć nową grupę. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Bardziej prywatny**: sprawdzanie nowych wiadomości odbywa się co 20 minut. Współdzielony z serwerem SimpleX Chat jest token urządzenia, lecz nie informacje o liczbie kontaktów lub wiadomości. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, wiadomości sprawdzane są co jakiś czas w tle (zależne od tego jak często korzystasz z aplikacji). No comment provided by engineer. @@ -357,11 +355,16 @@ **Uwaga**: NIE będziesz w stanie odzyskać lub zmienić kodu dostępu, jeśli go stracisz. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Zalecane**: do serwera powiadomień SimpleX Chat wysyłany jest token urządzenia i powiadomienia, lecz nie treść wiadomości, jej rozmiar lub od kogo ona jest. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + **Zeskanuj / Wklej link**: aby połączyć się za pomocą otrzymanego linku. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Uwaga**: Natychmiastowe powiadomienia push wymagają zapisania kodu dostępu w Keychain. @@ -387,11 +390,6 @@ \*pogrubiony* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - historia edycji. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sek @@ -446,7 +439,8 @@ 1 day 1 dzień - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 miesiąc - time interval + delete after time +time interval 1 week 1 tydzień - time interval + delete after time +time interval + + + 1 year + 1 roku + delete after time + + + 1-time link + link jednorazowy + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Link jednorazowy może być użyty *tylko z jednym kontaktem* - udostępnij go osobiście lub przez dowolny komunikator. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 sekund No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,19 +543,14 @@ Przerwać zmianę adresu? No comment provided by engineer. - - About SimpleX - O SimpleX - No comment provided by engineer. - About SimpleX Chat O SimpleX Chat No comment provided by engineer. - - About SimpleX address - O adresie SimpleX + + About operators + O operatorach No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Akceptuj accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Zaakceptuj warunki + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Akceptuj incognito accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Zaakceptowano warunki + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Błędy potwierdzenia No comment provided by engineer. + + Active + Aktywne + token status text + Active connections Aktywne połączenia @@ -600,14 +616,14 @@ 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 contact - Dodaj kontakt + + Add friends + Dodaj znajomych No comment provided by engineer. - - Add preset servers - Dodaj gotowe serwery + + Add list + Dodaj listę No comment provided by engineer. @@ -625,16 +641,41 @@ Dodaj serwery, skanując kody QR. No comment provided by engineer. + + Add team members + Dodaj członków zespołu + No comment provided by engineer. + Add to another device Dodaj do innego urządzenia No comment provided by engineer. + + Add to list + Dodaj do listy + No comment provided by engineer. + Add welcome message Dodaj wiadomość powitalną No comment provided by engineer. + + Add your team members to the conversations. + Dodaj członków zespołu do konwersacji. + No comment provided by engineer. + + + Added media & file servers + Dodano serwery multimediów i plików + No comment provided by engineer. + + + Added message servers + Dodano serwery wiadomości + No comment provided by engineer. + Additional accent Dodatkowy akcent @@ -660,6 +701,16 @@ Zmiana adresu zostanie przerwana. Użyty zostanie stary adres odbiorczy. No comment provided by engineer. + + Address or 1-time link? + Adres czy jednorazowy link? + No comment provided by engineer. + + + Address settings + Ustawienia adresu + No comment provided by engineer. + Admins can block a member for all. Administratorzy mogą blokować członka dla wszystkich. @@ -680,6 +731,11 @@ Zaawansowane ustawienia No comment provided by engineer. + + All + Wszystko + No comment provided by engineer. + All app data is deleted. Wszystkie dane aplikacji są usunięte. @@ -690,13 +746,18 @@ Wszystkie czaty i wiadomości zostaną usunięte - nie można tego cofnąć! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Wszystkie rozmowy zostaną usunięte z listy %@, a lista usunięta. + alert message + All data is erased when it is entered. Wszystkie dane są usuwane po jego wprowadzeniu. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Wszystkie dane są prywatne na Twoim urządzeniu. No comment provided by engineer. @@ -705,6 +766,11 @@ Wszyscy członkowie grupy pozostaną połączeni. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Wszystkie wiadomości i pliki są wysyłane **z szyfrowaniem end-to-end**, z bezpieczeństwem postkwantowym w wiadomościach bezpośrednich. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Wszystkie wiadomości zostaną usunięte – nie można tego cofnąć! @@ -723,6 +789,15 @@ All profiles Wszystkie profile + profile dropdown + + + All reports will be archived for you. + Wszystkie raporty zostaną dla Ciebie zarchiwizowane. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -800,6 +875,11 @@ Zezwól na nieodwracalne usunięcie wysłanych wiadomości. (24 godziny) No comment provided by engineer. + + Allow to report messsages to moderators. + Zezwól na zgłaszanie wiadomości moderatorom. + No comment provided by engineer. + Allow to send SimpleX links. Zezwól na wysyłanie linków SimpleX. @@ -880,11 +960,21 @@ Tworzony jest pusty profil czatu o podanej nazwie, a aplikacja otwiera się jak zwykle. No comment provided by engineer. + + Another reason + Inny powód + report reason + 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: %@ @@ -900,6 +990,11 @@ Aplikacja szyfruje nowe lokalne pliki (bez filmów). No comment provided by engineer. + + App group: + Grupa aplikacji: + No comment provided by engineer. + App icon Ikona aplikacji @@ -915,6 +1010,11 @@ Pin aplikacji został zastąpiony pinem samozniszczenia. No comment provided by engineer. + + App session + Sesja aplikacji + No comment provided by engineer. + App version Wersja aplikacji @@ -940,6 +1040,21 @@ Zastosuj dla No comment provided by engineer. + + Archive + Archiwizuj + No comment provided by engineer. + + + Archive %lld reports? + Archiwizować %lld reports? + No comment provided by engineer. + + + Archive all reports? + Archiwizować wszystkie zgłoszenia? + No comment provided by engineer. + Archive and upload Archiwizuj i prześlij @@ -950,6 +1065,21 @@ Archiwizuj kontakty aby porozmawiać później. No comment provided by engineer. + + Archive report + Archiwizuj zgłoszenie + No comment provided by engineer. + + + Archive report? + Archiwizować zgłoszenie? + No comment provided by engineer. + + + Archive reports + Archiwizuj zgłoszenia + swipe action + Archived contacts Zarchiwizowane kontakty @@ -1020,6 +1150,11 @@ Automatyczne akceptowanie obrazów No comment provided by engineer. + + Auto-accept settings + Ustawienia automatycznej akceptacji + alert title + Back Wstecz @@ -1045,11 +1180,25 @@ Zły hash wiadomości No comment provided by engineer. + + Better calls + Lepsze połączenia + No comment provided by engineer. + Better groups Lepsze grupy No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + Lepsze daty wiadomości. + No comment provided by engineer. + Better messages Lepsze wiadomości @@ -1060,6 +1209,25 @@ Lepsze sieciowanie No comment provided by engineer. + + Better notifications + Lepsze powiadomienia + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + Lepsze zabezpieczenia ✅ + No comment provided by engineer. + + + Better user experience + Lepszy interfejs użytkownika + No comment provided by engineer. + Black Czarny @@ -1140,11 +1308,32 @@ 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. + + Business address + Adres firmowy + No comment provided by engineer. + + + Business chats + Czaty biznesowe + No comment provided by engineer. + + + Businesses + Firmy + 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: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Połączenie już zakończone! @@ -1193,7 +1382,8 @@ Cancel Anuluj - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1403,7 @@ Cannot receive file Nie można odebrać pliku - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1420,15 @@ Zmień No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + Zmień profil czatu + authentication reason + Change database passphrase? Zmienić hasło bazy danych? @@ -1274,11 +1473,21 @@ Change self-destruct passcode Zmień pin samozniszczenia authentication reason - set passcode view +set passcode view - - Chat archive - Archiwum czatu + + Chat + Czat + No comment provided by engineer. + + + Chat already exists + Czat już istnieje + No comment provided by engineer. + + + Chat already exists! + Czat już istnieje! No comment provided by engineer. @@ -1303,12 +1512,12 @@ Chat database exported - Wyeksportowano bazę danych czatu + Wyeksportowano bazę danych czatów No comment provided by engineer. Chat database imported - Zaimportowano bazę danych czatu + Zaimportowano bazę danych czatów No comment provided by engineer. @@ -1341,20 +1550,50 @@ Preferencje czatu No comment provided by engineer. + + Chat preferences were changed. + Preferencje czatu zostały zmienione. + alert message + + + Chat profile + Profil użytkownika + No comment provided by engineer. + Chat theme Motyw czatu No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + Czat zostanie usunięty dla wszystkich członków – tej operacji nie można cofnąć! + 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ąć! + No comment provided by engineer. + Chats Czaty No comment provided by engineer. + + Check messages every 20 min. + Sprawdzaj wiadomości co 20 min. + No comment provided by engineer. + + + Check messages when allowed. + Sprawdź wiadomości, gdy będzie to dopuszczone. + No comment provided by engineer. + Check server address and try again. Sprawdź adres serwera i spróbuj ponownie. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1645,14 @@ Wyczyścić rozmowę? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? Wyczyścić prywatne notatki? @@ -1426,6 +1673,10 @@ Tryb koloru No comment provided by engineer. + + Community guidelines violation + report reason + Compare file Porównaj plik @@ -1441,14 +1692,45 @@ Zakończono No comment provided by engineer. + + Conditions accepted on: %@. + Warunki zaakceptowane dnia: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Warunki zostały zaakceptowane przez operatora(-ów): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Warunki zostały już zaakceptowane przez tego(-ych) operatora(-ów): **%@**. + No comment provided by engineer. + + + Conditions of use + Warunki użytkowania + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Skonfiguruj serwery ICE No comment provided by engineer. - - Configured %@ servers - Skonfigurowano %@ serwerów + + Configure server operators No comment provided by engineer. @@ -1501,6 +1783,10 @@ Potwierdź wgranie No comment provided by engineer. + + Confirmed + token status text + Connect Połącz @@ -1620,6 +1906,10 @@ To jest twój jednorazowy link! Stan połączenia i serwerów. No comment provided by engineer. + + Connection blocked + No comment provided by engineer. + Connection error Błąd połączenia @@ -1630,6 +1920,15 @@ To jest twój jednorazowy link! Błąd połączenia (UWIERZYTELNIANIE) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications Powiadomienia o połączeniu @@ -1640,6 +1939,14 @@ To jest twój jednorazowy link! Prośba o połączenie wysłana! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + No comment provided by engineer. + Connection terminated Połączenie zakończone @@ -1715,6 +2022,10 @@ To jest twój jednorazowy link! Kontakty mogą oznaczać wiadomości do usunięcia; będziesz mógł je zobaczyć. No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue Kontynuuj @@ -1740,6 +2051,11 @@ To jest twój jednorazowy link! Wersja rdzenia: v%@ No comment provided by engineer. + + Corner + Róg + No comment provided by engineer. + Correct name to %@? Poprawić imię na %@? @@ -1750,6 +2066,10 @@ To jest twój jednorazowy link! Utwórz No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Utwórz adres SimpleX @@ -1760,11 +2080,6 @@ To jest twój jednorazowy link! Utwórz grupę używając losowego profilu. No comment provided by engineer. - - Create an address to let people connect with you. - Utwórz adres, aby ludzie mogli się z Tobą połączyć. - No comment provided by engineer. - Create file Utwórz plik @@ -1785,6 +2100,10 @@ To jest twój jednorazowy link! Utwórz link No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Utwórz nowy profil w [aplikacji desktopowej](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2144,6 @@ To jest twój jednorazowy link! Utworzony o: %@ copied message info - - Created on %@ - Utworzony w dniu %@ - No comment provided by engineer. - Creating archive link Tworzenie linku archiwum @@ -1845,6 +2159,10 @@ To jest twój jednorazowy link! Aktualny Pin No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Obecne hasło… @@ -1865,6 +2183,10 @@ To jest twój jednorazowy link! Niestandardowy czas No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Dostosuj motyw @@ -1996,8 +2318,8 @@ To jest twój jednorazowy link! Delete Usuń - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2356,12 @@ To jest twój jednorazowy link! Usuń i powiadom kontakt No comment provided by engineer. - - Delete archive - Usuń archiwum + + Delete chat No comment provided by engineer. - - Delete chat archive? - Usunąć archiwum czatu? + + Delete chat messages from your device. No comment provided by engineer. @@ -2054,6 +2374,10 @@ To jest twój jednorazowy link! Usunąć profil czatu? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Usuń połączenie @@ -2129,6 +2453,10 @@ To jest twój jednorazowy link! Usunąć link? No comment provided by engineer. + + Delete list? + alert title + Delete member message? Usunąć wiadomość członka? @@ -2142,7 +2470,7 @@ To jest twój jednorazowy link! Delete messages Usuń wiadomości - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2487,10 @@ To jest twój jednorazowy link! Usunąć starą bazę danych? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Usunąć oczekujące połączenie? @@ -2174,6 +2506,10 @@ To jest twój jednorazowy link! Usuń kolejkę server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. Usuń do 20 wiadomości na raz. @@ -2209,6 +2545,10 @@ To jest twój jednorazowy link! Błędy usuwania No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Dostarczenie @@ -2309,8 +2649,12 @@ To jest twój jednorazowy link! Bezpośrednie wiadomości chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + + + Direct messages between members are prohibited. Bezpośrednie wiadomości między członkami są zabronione w tej grupie. No comment provided by engineer. @@ -2324,6 +2668,14 @@ To jest twój jednorazowy link! Wyłącz blokadę SimpleX authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all Wyłącz dla wszystkich @@ -2349,8 +2701,8 @@ To jest twój jednorazowy link! Znikające wiadomości są zabronione na tym czacie. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Znikające wiadomości są zabronione w tej grupie. No comment provided by engineer. @@ -2409,6 +2761,15 @@ To jest twój jednorazowy link! Nie wysyłaj historii do nowych członków. No comment provided by engineer. + + Do not use credentials with proxy. + Nie używaj danych logowania do proxy. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address Nie twórz adresu @@ -2419,11 +2780,19 @@ To jest twój jednorazowy link! Nie włączaj No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again Nie pokazuj ponownie No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat Obniż wersję i otwórz czat @@ -2432,7 +2801,8 @@ To jest twój jednorazowy link! Download Pobierz - chat item action + alert button +chat item action Download errors @@ -2449,6 +2819,11 @@ To jest twój jednorazowy link! Pobierz plik server test step + + Download files + Pobierz pliki + alert action + Downloaded Pobrane @@ -2479,6 +2854,10 @@ To jest twój jednorazowy link! Czas trwania No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Edytuj @@ -2499,6 +2878,10 @@ To jest twój jednorazowy link! Włącz (zachowaj nadpisania) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Włącz blokadę SimpleX @@ -2512,7 +2895,7 @@ To jest twój jednorazowy link! Enable automatic message deletion? Czy włączyć automatyczne usuwanie wiadomości? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3022,10 @@ To jest twój jednorazowy link! Renegocjacja szyfrowania nie powiodła się. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Wprowadź Pin @@ -2704,26 +3091,34 @@ To jest twój jednorazowy link! Błąd przerwania zmiany adresu No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Błąd przyjmowania prośby o kontakt No comment provided by engineer. - - Error accessing database file - Błąd dostępu do pliku bazy danych - No comment provided by engineer. - Error adding member(s) Błąd dodawania członka(ów) No comment provided by engineer. + + Error adding server + alert title + Error changing address Błąd zmiany adresu No comment provided by engineer. + + Error changing connection profile + Błąd zmiany połączenia profilu + No comment provided by engineer. + Error changing role Błąd zmiany roli @@ -2734,6 +3129,15 @@ To jest twój jednorazowy link! Błąd zmiany ustawienia No comment provided by engineer. + + Error changing to incognito! + Błąd zmiany na incognito! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później. @@ -2754,6 +3158,10 @@ To jest twój jednorazowy link! Błąd tworzenia linku grupy No comment provided by engineer. + + Error creating list + alert title + Error creating member contact Błąd tworzenia kontaktu członka @@ -2769,6 +3177,10 @@ To jest twój jednorazowy link! Błąd tworzenia profilu! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file Błąd odszyfrowania pliku @@ -2849,9 +3261,13 @@ To jest twój jednorazowy link! Błąd dołączenia do grupy No comment provided by engineer. - - Error loading %@ servers - Błąd ładowania %@ serwerów + + Error loading servers + alert title + + + Error migrating settings + Błąd migracji ustawień No comment provided by engineer. @@ -2862,7 +3278,7 @@ To jest twój jednorazowy link! Error receiving file Błąd odbioru pliku - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3290,33 @@ To jest twój jednorazowy link! Błąd ponownego łączenia serwerów No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member Błąd usuwania członka No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics Błąd resetowania statystyk No comment provided by engineer. - - Error saving %@ servers - Błąd zapisu %@ serwerów - No comment provided by engineer. - Error saving ICE servers Błąd zapisu serwerów ICE No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile Błąd zapisu profilu grupy @@ -2909,6 +3332,10 @@ To jest twój jednorazowy link! Błąd zapisu hasła do pęku kluczy No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Błąd zapisywania ustawień @@ -2954,16 +3381,25 @@ To jest twój jednorazowy link! Błąd zatrzymania czatu No comment provided by engineer. + + Error switching profile + Błąd zmiany profilu + No comment provided by engineer. + Error switching profile! Błąd przełączania profilu! - No comment provided by engineer. + alertTitle Error synchronizing connection Błąd synchronizacji połączenia No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link Błąd aktualizacji linku grupy @@ -2974,6 +3410,10 @@ To jest twój jednorazowy link! Błąd aktualizacji wiadomości No comment provided by engineer. + + Error updating server + alert title + Error updating settings Błąd aktualizacji ustawień @@ -3002,8 +3442,9 @@ To jest twój jednorazowy link! Error: %@ Błąd: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3461,10 @@ To jest twój jednorazowy link! Błędy No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Nawet po wyłączeniu w rozmowie. @@ -3035,6 +3480,10 @@ To jest twój jednorazowy link! Rozszerz chat item action + + Expired + token status text + Export database Eksportuj bazę danych @@ -3075,20 +3524,44 @@ To jest twój jednorazowy link! Szybko i bez czekania aż nadawca będzie online! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. Szybsze dołączenie i bardziej niezawodne wiadomości. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite Ulubione swipe action + + Favorites + No comment provided by engineer. + File error Błąd pliku - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Błędy pliku: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,8 +3618,8 @@ To jest twój jednorazowy link! Pliki i media chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Pliki i media są zabronione w tej grupie. No comment provided by engineer. @@ -3215,21 +3688,64 @@ To jest twój jednorazowy link! Naprawa nie jest obsługiwana przez członka grupy No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + servers error + For console Dla konsoli No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Przekaż dalej chat item action + + Forward %d message(s)? + Przekazać %d wiadomość(i)? + alert title + Forward and save messages Przesyłaj dalej i zapisuj wiadomości No comment provided by engineer. + + Forward messages + Przekaż wiadomości + alert action + + + Forward messages without files? + Przekazać wiadomości bez plików? + alert message + + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Przekazane dalej @@ -3240,6 +3756,11 @@ To jest twój jednorazowy link! Przekazane dalej od No comment provided by engineer. + + Forwarding %lld messages + Przekazywanie %lld wiadomości + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później. @@ -3289,11 +3810,6 @@ Błąd: %2$@ Pełna nazwa (opcjonalna) No comment provided by engineer. - - Full name: - Pełna nazwa: - No comment provided by engineer. - Fully decentralized – visible only to members. W pełni zdecentralizowana – widoczna tylko dla członków. @@ -3314,6 +3830,10 @@ Błąd: %2$@ GIF-y i naklejki No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! Dzień dobry! @@ -3379,41 +3899,6 @@ Błąd: %2$@ Linki grupowe No comment provided by engineer. - - Group members can add message reactions. - Członkowie grupy mogą dodawać reakcje wiadomości. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny) - No comment provided by engineer. - - - Group members can send SimpleX links. - Członkowie grupy mogą wysyłać linki SimpleX. - No comment provided by engineer. - - - Group members can send direct messages. - Członkowie grupy mogą wysyłać bezpośrednie wiadomości. - No comment provided by engineer. - - - Group members can send disappearing messages. - Członkowie grupy mogą wysyłać znikające wiadomości. - No comment provided by engineer. - - - Group members can send files and media. - Członkowie grupy mogą wysyłać pliki i media. - No comment provided by engineer. - - - Group members can send voice messages. - Członkowie grupy mogą wysyłać wiadomości głosowe. - No comment provided by engineer. - Group message: Wiadomość grupowa: @@ -3454,11 +3939,19 @@ Błąd: %2$@ Grupa zostanie usunięta dla Ciebie - nie można tego cofnąć! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Pomoc No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Ukryte @@ -3509,10 +4002,17 @@ Błąd: %2$@ Jak działa SimpleX No comment provided by engineer. + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + No comment provided by engineer. + How it works - Jak to działa - No comment provided by engineer. + alert button How to @@ -3539,6 +4039,11 @@ Błąd: %2$@ Serwery ICE (po jednym na linię) No comment provided by engineer. + + IP address + Adres IP + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Jeśli nie możesz spotkać się osobiście, pokaż kod QR w rozmowie wideo lub udostępnij link. @@ -3579,8 +4084,8 @@ Błąd: %2$@ Natychmiast No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Odporność na spam i nadużycia No comment provided by engineer. @@ -3614,6 +4119,11 @@ Błąd: %2$@ Importowanie archiwum No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Ulepszona dostawa wiadomości @@ -3644,6 +4154,14 @@ Błąd: %2$@ Dźwięki w rozmowie No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Incognito @@ -3714,6 +4232,11 @@ Błąd: %2$@ Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Natychmiastowo + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4244,6 @@ Błąd: %2$@ No comment provided by engineer. - - Instantly - Natychmiastowo - No comment provided by engineer. - Interface Interfejs @@ -3736,6 +4254,26 @@ Błąd: %2$@ Kolory interfejsu No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code Nieprawidłowy kod QR @@ -3774,7 +4312,7 @@ Błąd: %2$@ Invalid server address! Nieprawidłowy adres serwera! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4334,10 @@ Błąd: %2$@ Zaproś członków No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Zaproś do grupy @@ -3811,8 +4353,8 @@ Błąd: %2$@ Nieodwracalne usuwanie wiadomości jest na tym czacie zabronione. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Nieodwracalne usuwanie wiadomości jest w tej grupie zabronione. No comment provided by engineer. @@ -3902,7 +4444,7 @@ To jest twój link do grupy %@! Keep Zachowaj - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4459,7 @@ To jest twój link do grupy %@! Keep unused invitation? Zachować nieużyte zaproszenie? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4496,14 @@ To jest twój link do grupy %@! Opuść swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Opuść grupę @@ -3994,6 +4544,18 @@ To jest twój link do grupy %@! Połączone komputery No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Wiadomość na żywo! @@ -4004,11 +4566,6 @@ To jest twój link do grupy %@! Wiadomości na żywo No comment provided by engineer. - - Local - Lokalnie - No comment provided by engineer. - Local name Nazwa lokalna @@ -4029,11 +4586,6 @@ To jest twój link do grupy %@! Tryb blokady No comment provided by engineer. - - Make a private connection - Nawiąż prywatne połączenie - No comment provided by engineer. - Make one message disappear Spraw, aby jedna wiadomość zniknęła @@ -4044,21 +4596,11 @@ To jest twój link do grupy %@! Ustaw profil jako prywatny! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Upewnij się, że adresy serwerów %@ są w poprawnym formacie, rozdzielone liniami i nie są zduplikowane (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Upewnij się, że adresy serwerów WebRTC ICE są w poprawnym formacie, rozdzielone liniami i nie są zduplikowane. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Wiele osób pytało: *jeśli SimpleX nie ma identyfikatora użytkownika, jak może dostarczać wiadomości?* - No comment provided by engineer. - Mark deleted for everyone Oznacz jako usunięty dla wszystkich @@ -4104,6 +4646,14 @@ To jest twój link do grupy %@! Członek nieaktywny item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Rola członka grupy zostanie zmieniona na "%@". Wszyscy członkowie grupy zostaną powiadomieni. @@ -4114,11 +4664,58 @@ To jest twój link do grupy %@! Rola członka zostanie zmieniona na "%@". Członek otrzyma nowe zaproszenie. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Członek zostanie usunięty z grupy - nie można tego cofnąć! No comment provided by engineer. + + Members can add message reactions. + Członkowie grupy mogą dodawać reakcje wiadomości. + 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) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Członkowie grupy mogą wysyłać linki SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Członkowie grupy mogą wysyłać bezpośrednie wiadomości. + No comment provided by engineer. + + + Members can send disappearing messages. + Członkowie grupy mogą wysyłać znikające wiadomości. + No comment provided by engineer. + + + Members can send files and media. + Członkowie grupy mogą wysyłać pliki i media. + No comment provided by engineer. + + + Members can send voice messages. + Członkowie grupy mogą wysyłać wiadomości głosowe. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus Menu @@ -4169,8 +4766,8 @@ To jest twój link do grupy %@! Reakcje wiadomości są zabronione na tym czacie. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Reakcje wiadomości są zabronione w tej grupie. No comment provided by engineer. @@ -4184,6 +4781,11 @@ To jest twój link do grupy %@! Serwery wiadomości No comment provided by engineer. + + Message shape + Kształt wiadomości + No comment provided by engineer. + Message source remains private. Źródło wiadomości pozostaje prywatne. @@ -4224,6 +4826,10 @@ To jest twój link do grupy %@! Wiadomości od %@ zostaną pokazane! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received Otrzymane wiadomości @@ -4234,6 +4840,11 @@ To jest twój link do grupy %@! Wysłane wiadomości No comment provided by engineer. + + Messages were deleted after you selected them. + Wiadomości zostały usunięte po wybraniu ich. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. @@ -4299,9 +4910,9 @@ To jest twój link do grupy %@! Migracja została zakończona No comment provided by engineer. - - Migrations: %@ - Migracje: %@ + + Migrations: + Migracje: No comment provided by engineer. @@ -4319,6 +4930,10 @@ To jest twój link do grupy %@! Moderowany o: %@ copied message info + + More + swipe action + More improvements are coming soon! Więcej ulepszeń już wkrótce! @@ -4329,6 +4944,10 @@ To jest twój link do grupy %@! Bardziej niezawodne połączenia sieciowe. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Najprawdopodobniej to połączenie jest usunięte. @@ -4342,7 +4961,11 @@ To jest twój link do grupy %@! Mute Wycisz - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4364,6 +4987,10 @@ To jest twój link do grupy %@! Połączenie z siecią No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej. @@ -4374,6 +5001,10 @@ To jest twój link do grupy %@! Zarządzenie sieciowe No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Ustawienia sieci @@ -4384,11 +5015,25 @@ To jest twój link do grupy %@! Status sieci No comment provided by engineer. + + New + token status text + New Passcode Nowy Pin No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Nowe poświadczenia SOCKS będą używane przy każdym uruchomieniu aplikacji. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Dla każdego serwera zostaną użyte nowe poświadczenia SOCKS. + No comment provided by engineer. + New chat Nowy czat @@ -4409,11 +5054,6 @@ To jest twój link do grupy %@! Nowy kontakt: notification - - New database archive - Nowe archiwum bazy danych - No comment provided by engineer. - New desktop app! Nowa aplikacja desktopowa! @@ -4424,6 +5064,10 @@ To jest twój link do grupy %@! Nowa wyświetlana nazwa No comment provided by engineer. + + New events + notification + New in %@ Nowość w %@ @@ -4449,6 +5093,10 @@ To jest twój link do grupy %@! Nowe hasło… No comment provided by engineer. + + New server + No comment provided by engineer. + No Nie @@ -4459,6 +5107,18 @@ To jest twój link do grupy %@! Brak hasła aplikacji Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Nie wybrano kontaktów @@ -4504,31 +5164,96 @@ To jest twój link do grupy %@! Brak informacji, spróbuj przeładować No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection Brak połączenia z siecią No comment provided by engineer. + + No permission to record speech + Brak zezwoleń do nagrania rozmowy + No comment provided by engineer. + + + No permission to record video + Brak zezwoleń do nagrania wideo + No comment provided by engineer. + No permission to record voice message Brak uprawnień do nagrywania wiadomości głosowej No comment provided by engineer. + + No push server + Lokalnie + No comment provided by engineer. + No received or sent files Brak odebranych lub wysłanych plików No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + Brak identyfikatorów użytkownika. + No comment provided by engineer. + Not compatible! Nie kompatybilny! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected Nic nie jest zaznaczone No comment provided by engineer. + + Nothing to forward! + Nic do przekazania! + alert title + Notifications Powiadomienia @@ -4539,6 +5264,18 @@ To jest twój link do grupy %@! Powiadomienia są wyłączone! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5298,13 @@ To jest twój link do grupy %@! Ok Ok - No comment provided by engineer. + alert button Old database Stara baza danych No comment provided by engineer. - - Old database archive - Stare archiwum bazy danych - No comment provided by engineer. - One-time invitation link Jednorazowy link zaproszenia @@ -4597,8 +5329,12 @@ Wymaga włączenia VPN. Hosty onion nie będą używane. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**. No comment provided by engineer. @@ -4622,6 +5358,14 @@ Wymaga włączenia VPN. Tylko właściciele grup mogą włączyć wiadomości głosowe. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Tylko Ty możesz dodawać reakcje wiadomości. @@ -4675,13 +5419,17 @@ Wymaga włączenia VPN. Open Otwórz - No comment provided by engineer. + alert action Open Settings Otwórz Ustawienia No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Otwórz czat @@ -4692,36 +5440,41 @@ Wymaga włączenia VPN. Otwórz konsolę czatu authentication reason + + Open conditions + No comment provided by engineer. + Open group Grupa otwarta No comment provided by engineer. + + Open link? + alert title + Open migration to another device Otwórz migrację na innym urządzeniu authentication reason - - Open server settings - Otwórz ustawienia serwera - No comment provided by engineer. - - - Open user profiles - Otwórz profile użytkownika - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Otwarto źródłowy protokół i kod - każdy może uruchomić serwery. - No comment provided by engineer. - Opening app… Otwieranie aplikacji… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link Lub wklej link archiwum @@ -4742,15 +5495,25 @@ Wymaga włączenia VPN. Lub pokaż ten kod No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other Inne No comment provided by engineer. - - Other %@ servers - Inne %@ serwery - No comment provided by engineer. + + Other file errors: +%@ + Inne błędy pliku: +%@ + alert message PING count @@ -4787,6 +5550,11 @@ Wymaga włączenia VPN. Pin ustawiony! No comment provided by engineer. + + Password + Hasło + No comment provided by engineer. + Password to show Hasło do wyświetlenia @@ -4822,13 +5590,8 @@ Wymaga włączenia VPN. Oczekujące No comment provided by engineer. - - People can connect to you only via the links you share. - Ludzie mogą się z Tobą połączyć tylko poprzez linki, które udostępniasz. - No comment provided by engineer. - - - Periodically + + Periodic Okresowo No comment provided by engineer. @@ -4931,11 +5694,28 @@ Błąd: %@ Przechowuj kod dostępu w bezpieczny sposób, w przypadku jego utraty NIE będzie można go zmienić. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Polski interfejs No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy @@ -4946,16 +5726,15 @@ Błąd: %@ Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami. No comment provided by engineer. - - Preset server - Wstępnie ustawiony serwer - No comment provided by engineer. - Preset server address Wstępnie ustawiony adres serwera No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Podgląd @@ -4971,16 +5750,32 @@ Błąd: %@ Prywatność i bezpieczeństwo No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + + + Privacy policy and conditions of use. + 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. + No comment provided by engineer. + Private filenames Prywatne nazwy plików No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing Trasowanie prywatnych wiadomości @@ -5021,16 +5816,6 @@ Błąd: %@ Zdjęcia profilowe No comment provided by engineer. - - Profile name - Nazwa profilu - No comment provided by engineer. - - - Profile name: - Nazwa profilu: - No comment provided by engineer. - Profile password Hasło profilu @@ -5044,7 +5829,7 @@ Błąd: %@ Profile update will be sent to your contacts. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5851,10 @@ Błąd: %@ Zabroń reakcje wiadomości. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. Zabroń wysyłania linków SimpleX. @@ -5133,6 +5922,11 @@ Włącz w ustawianiach *Sieć i serwery* . Serwery trasowane przez proxy No comment provided by engineer. + + Proxy requires password + Proxy wymaga hasła + No comment provided by engineer. + Push notifications Powiadomienia push @@ -5173,26 +5967,21 @@ 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/app-settings.html#your-simplex-contact-address). - Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Przeczytaj więcej na naszym repozytorium GitHub. - 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). @@ -5323,11 +6112,23 @@ Włącz w ustawianiach *Sieć i serwery* . Zmniejszone zużycie baterii No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Odrzuć reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6155,11 @@ Włącz w ustawianiach *Sieć i serwery* . Usuń No comment provided by engineer. + + Remove archive? + Usunąć archiwum? + No comment provided by engineer. + Remove image Usuń obraz @@ -5419,6 +6225,46 @@ Włącz w ustawianiach *Sieć i serwery* . Odpowiedz chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Wymagane @@ -5504,6 +6350,10 @@ Włącz w ustawianiach *Sieć i serwery* . Ujawnij chat item action + + Review conditions + No comment provided by engineer. + Revoke Odwołaj @@ -5534,6 +6384,11 @@ Włącz w ustawianiach *Sieć i serwery* . Serwer SMP No comment provided by engineer. + + SOCKS proxy + Proxy SOCKS + No comment provided by engineer. + Safely receive files Bezpiecznie otrzymuj pliki @@ -5547,17 +6402,18 @@ Włącz w ustawianiach *Sieć i serwery* . Save Zapisz - chat item action + alert button +chat item action Save (and notify contacts) Zapisz (i powiadom kontakty) - No comment provided by engineer. + alert button Save and notify contact Zapisz i powiadom kontakt - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6430,15 @@ Włącz w ustawianiach *Sieć i serwery* . Zapisz i zaktualizuj profil grupowy No comment provided by engineer. - - Save archive - Zapisz archiwum - No comment provided by engineer. - - - Save auto-accept settings - Zapisz ustawienia automatycznej akceptacji - No comment provided by engineer. - Save group profile Zapisz profil grupy No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Zapisz hasło i otwórz czat @@ -5602,7 +6452,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save preferences? Zapisać preferencje? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6467,18 @@ Włącz w ustawianiach *Sieć i serwery* . Save servers? Zapisać serwery? - No comment provided by engineer. - - - Save settings? - Zapisać ustawienia? - No comment provided by engineer. + alert title Save welcome message? Zapisać wiadomość powitalną? No comment provided by engineer. + + Save your profile? + Zapisać Twój profil? + alert title + Saved Zapisane @@ -5649,6 +6499,11 @@ Włącz w ustawianiach *Sieć i serwery* . Zachowano wiadomość message info title + + Saving %lld messages + Zapisywanie %lld wiadomości + No comment provided by engineer. + Scale Skaluj @@ -5729,6 +6584,11 @@ Włącz w ustawianiach *Sieć i serwery* . Wybierz chat item action + + Select chat profile + Wybierz profil czatu + No comment provided by engineer. + Selected %lld Zaznaczono %lld @@ -5819,9 +6679,8 @@ Włącz w ustawianiach *Sieć i serwery* . Wyślij powiadomienia No comment provided by engineer. - - Send notifications: - Wyślij powiadomienia: + + Send private reports No comment provided by engineer. @@ -5847,7 +6706,7 @@ Włącz w ustawianiach *Sieć i serwery* . Sender cancelled file transfer. Nadawca anulował transfer pliku. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6803,15 @@ Włącz w ustawianiach *Sieć i serwery* . Wysłano przez proxy No comment provided by engineer. + + Server + Serwer + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address Adres serwera @@ -5959,6 +6827,18 @@ Włącz w ustawianiach *Sieć i serwery* . Adres serwera jest niekompatybilny z ustawieniami sieci: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło @@ -6014,6 +6894,10 @@ Włącz w ustawianiach *Sieć i serwery* . Ustaw 1 dzień No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Ustaw nazwę kontaktu… @@ -6034,6 +6918,10 @@ Włącz w ustawianiach *Sieć i serwery* . Ustaw go zamiast uwierzytelniania systemowego. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Ustaw pin @@ -6064,6 +6952,11 @@ Włącz w ustawianiach *Sieć i serwery* . Ustawienia No comment provided by engineer. + + Settings were changed. + Ustawienia zostały zmienione. + alert message + Shape profile images Kształtuj obrazy profilowe @@ -6072,22 +6965,35 @@ Włącz w ustawianiach *Sieć i serwery* . Share Udostępnij - chat item action + alert action +chat item action Share 1-time link Udostępnij 1-razowy link No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Udostępnij adres No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Udostępnić adres kontaktom? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7005,11 @@ Włącz w ustawianiach *Sieć i serwery* . Udostępnij link No comment provided by engineer. + + Share profile + Udostępnij profil + No comment provided by engineer. + Share this 1-time invite link Udostępnij ten jednorazowy link @@ -6114,6 +7025,10 @@ Włącz w ustawianiach *Sieć i serwery* . Udostępnij kontaktom No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Pokaż kod QR @@ -6169,6 +7084,10 @@ Włącz w ustawianiach *Sieć i serwery* . Adres SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Bezpieczeństwo SimpleX Chat zostało zaudytowane przez Trail of Bits. @@ -6199,6 +7118,18 @@ Włącz w ustawianiach *Sieć i serwery* . Adres SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address Adres kontaktowy SimpleX @@ -6219,8 +7150,8 @@ Włącz w ustawianiach *Sieć i serwery* . Linki SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Linki SimpleX są zablokowane na tej grupie. No comment provided by engineer. @@ -6234,6 +7165,10 @@ Włącz w ustawianiach *Sieć i serwery* . Zaproszenie jednorazowe SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Uproszczony tryb incognito @@ -6264,6 +7199,11 @@ Włącz w ustawianiach *Sieć i serwery* . Łagodny blur media + + Some app settings were not migrated. + Niektóre ustawienia aplikacji nie zostały zmigrowane. + No comment provided by engineer. + Some file(s) were not exported: Niektóre plik(i) nie zostały wyeksportowane: @@ -6279,11 +7219,21 @@ Włącz w ustawianiach *Sieć i serwery* . Podczas importu wystąpiły niekrytyczne błędy: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Ktoś notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. Kwadrat, okrąg lub cokolwiek pomiędzy. @@ -6329,11 +7279,6 @@ Włącz w ustawianiach *Sieć i serwery* . Zatrzymaj czat No comment provided by engineer. - - Stop chat to enable database actions - Zatrzymaj czat, aby umożliwić działania na bazie danych - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Zatrzymaj czat, aby wyeksportować, zaimportować lub usunąć bazę danych czatu. Podczas zatrzymania chatu nie będzie można odbierać ani wysyłać wiadomości. @@ -6362,18 +7307,22 @@ Włącz w ustawianiach *Sieć i serwery* . Stop sharing Przestań udostępniać - No comment provided by engineer. + alert action Stop sharing address? Przestać udostępniać adres? - No comment provided by engineer. + alert title Stopping chat Zatrzymywanie czatu No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong Silne @@ -6404,6 +7353,14 @@ Włącz w ustawianiach *Sieć i serwery* . Wspieraj SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System System @@ -6424,6 +7381,10 @@ Włącz w ustawianiach *Sieć i serwery* . Limit czasu połączenia TCP No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7400,20 @@ Włącz w ustawianiach *Sieć i serwery* . TCP_KEEPINTVL No comment provided by engineer. + + Tail + Ogon + No comment provided by engineer. + Take picture Zrób zdjęcie No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Naciśnij przycisk @@ -6482,13 +7452,17 @@ Włącz w ustawianiach *Sieć i serwery* . Temporary file error Tymczasowy błąd pliku - No comment provided by engineer. + file error alert title Test failed at step %@. Test nie powiódł się na etapie %@. server test failure + + Test notifications + No comment provided by engineer. + Test server Przetestuj serwer @@ -6502,7 +7476,7 @@ Włącz w ustawianiach *Sieć i serwery* . Tests failed! Testy nie powiodły się! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7493,6 @@ Włącz w ustawianiach *Sieć i serwery* . Podziękowania dla użytkowników - wkład za pośrednictwem Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Pierwsza platforma bez żadnych identyfikatorów użytkowników – z założenia prywatna. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7505,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Aplikacja zapyta o potwierdzenie pobierania od nieznanych serwerów plików (poza .onion). @@ -6551,6 +7524,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Zaakceptowane przez Ciebie połączenie zostanie anulowane! @@ -6571,6 +7548,11 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. Hash poprzedniej wiadomości jest inny. @@ -6596,19 +7578,17 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków. No comment provided by engineer. - - The next generation of private messaging - Następna generacja prywatnych wiadomości - 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 profile is only shared with your contacts. - Profil jest udostępniany tylko Twoim kontaktom. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6626,16 +7606,29 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Serwery dla nowych połączeń bieżącego profilu czatu **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Tekst, który wkleiłeś nie jest linkiem SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + Przesłane archiwum bazy danych zostanie trwale usunięte z serwerów. + No comment provided by engineer. + Themes Motywy No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Te ustawienia dotyczą Twojego bieżącego profilu **%@**. @@ -6656,6 +7649,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Tego działania nie można cofnąć - wiadomości wysłane i odebrane wcześniej niż wybrane zostaną usunięte. Może to potrwać kilka minut. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Tego działania nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone. @@ -6701,11 +7698,19 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom To jest twój jednorazowy link! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Ten link dostał użyty z innym urządzeniem mobilnym, proszę stworzyć nowy link na komputerze. No comment provided by engineer. + + This message was deleted or not received yet. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. To ustawienie dotyczy wiadomości Twojego bieżącego profilu czatu **%@**. @@ -6736,9 +7741,8 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aby nawiązać nowe połączenie No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6758,6 +7762,25 @@ You will be prompted to complete authentication before this feature is enabled.< Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Aby nagrać rozmowę, proszę zezwolić na użycie Mikrofonu. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Aby nagrać wideo, proszę zezwolić na użycie Aparatu. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Aby nagrać wiadomość głosową należy udzielić zgody na użycie Mikrofonu. @@ -6768,11 +7791,19 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Aby ujawnić Twój ukryty profil, wprowadź pełne hasło w pole wyszukiwania na stronie **Twoich profili czatu**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Aby obsługiwać natychmiastowe powiadomienia push, należy zmigrować bazę danych czatu. No comment provided by engineer. + + 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. Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach. @@ -6788,6 +7819,10 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Przełącz incognito przy połączeniu. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity Nieprzezroczystość paska narzędzi @@ -6863,6 +7898,10 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Odblokować członka? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Nieoczekiwany stan migracji @@ -6911,7 +7950,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. Unknown servers! Nieznane serwery! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +7987,17 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Unmute Wyłącz wyciszenie - swipe action + notification label action Unread Nieprzeczytane swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Do nowych członków wysyłanych jest do 100 ostatnich wiadomości. @@ -6980,6 +8023,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Zaktualizować ustawienia? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Aktualizacja ustawień spowoduje ponowne połączenie klienta ze wszystkimi serwerami. @@ -7020,16 +8067,33 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wgrywanie archiwum No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Użyj hostów .onion No comment provided by engineer. + + Use SOCKS proxy + Użyj proxy SOCKS + No comment provided by engineer. + Use SimpleX Chat servers? Użyć serwerów SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Użyj czatu @@ -7040,6 +8104,14 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj obecnego profilu No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Użyj dla nowych połączeń @@ -7080,6 +8152,14 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj serwera No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Używaj aplikacji podczas połączenia. @@ -7090,9 +8170,8 @@ 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. - - User profile - Profil użytkownika + + Use web port No comment provided by engineer. @@ -7100,6 +8179,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wybór użytkownika No comment provided by engineer. + + Username + Nazwa użytkownika + No comment provided by engineer. + Using SimpleX Chat servers. Używanie serwerów SimpleX Chat. @@ -7170,11 +8254,19 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Filmy i pliki do 1gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Pokaż kod bezpieczeństwa No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Widoczna historia @@ -7190,8 +8282,8 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wiadomości głosowe są zabronione na tym czacie. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Wiadomości głosowe są zabronione w tej grupie. No comment provided by engineer. @@ -7285,9 +8377,8 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Podczas łączenia połączeń audio i wideo. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7333,7 +8424,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaźników XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8451,6 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Serwer XFTP No comment provided by engineer. - - You - Ty - No comment provided by engineer. - You **must not** use the same database on two devices. **Nie możesz** używać tej samej bazy na dwóch urządzeniach. @@ -7390,6 +8476,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Jesteś już połączony z %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Już się łączysz z %@. @@ -7452,6 +8542,10 @@ Powtórzyć prośbę dołączenia? Możesz to zmienić w ustawieniach wyglądu. No comment provided by engineer. + + You can configure servers via settings. + No comment provided by engineer. + You can create it later Możesz go utworzyć później @@ -7492,6 +8586,10 @@ Powtórzyć prośbę dołączenia? Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach. @@ -7507,11 +8605,6 @@ Powtórzyć prośbę dołączenia? Możesz udostępnić ten adres Twoim kontaktom, aby umożliwić im połączenie z **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Możesz rozpocząć czat poprzez Ustawienia aplikacji / Baza danych lub poprzez ponowne uruchomienie aplikacji @@ -7535,23 +8628,23 @@ Powtórzyć prośbę dołączenia? You can view invitation link again in connection details. Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia. - No comment provided by engineer. + alert message You can't send messages! Nie możesz wysyłać wiadomości! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Kontrolujesz przez który serwer(y) **odbierać** wiadomości, Twoje kontakty - serwery, których używasz do wysyłania im wiadomości. - 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ć. + No comment provided by engineer. + You have already requested connection via this address! Już prosiłeś o połączenie na ten adres! @@ -7619,6 +8712,10 @@ Powtórzyć prośbę połączenia? Wysłałeś zaproszenie do grupy No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! Zostaniesz połączony do grupy, gdy urządzenie gospodarza grupy będzie online, proszę czekać lub sprawdzić później! @@ -7654,6 +8751,10 @@ Powtórzyć prośbę połączenia? Nadal będziesz otrzymywać połączenia i powiadomienia z wyciszonych profili, gdy są one aktywne. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Przestaniesz otrzymywać wiadomości od tej grupy. Historia czatu zostanie zachowana. @@ -7674,31 +8775,16 @@ Powtórzyć prośbę połączenia? Używasz profilu incognito dla tej grupy - aby zapobiec udostępnianiu głównego profilu zapraszanie kontaktów jest zabronione No comment provided by engineer. - - Your %@ servers - Twoje serwery %@ - No comment provided by engineer. - Your ICE servers Twoje serwery ICE No comment provided by engineer. - - Your SMP servers - Twoje serwery SMP - No comment provided by engineer. - Your SimpleX address Twój adres SimpleX No comment provided by engineer. - - Your XFTP servers - Twoje serwery XFTP - No comment provided by engineer. - Your calls Twoje połączenia @@ -7714,11 +8800,21 @@ Powtórzyć prośbę połączenia? Baza danych czatu nie jest szyfrowana - ustaw hasło, aby ją zaszyfrować. No comment provided by engineer. + + Your chat preferences + Twoje preferencje czatu + alert title + Your chat profiles Twoje profile czatu No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@). @@ -7734,6 +8830,11 @@ Powtórzyć prośbę połączenia? Twoje kontakty pozostaną połączone. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Twoje poświadczenia mogą zostać wysłane niezaszyfrowane. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Twoja obecna baza danych czatu zostanie usunięta i zastąpiona zaimportowaną. @@ -7764,33 +8865,36 @@ Powtórzyć prośbę połączenia? Twój profil **%@** zostanie udostępniony. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. -Serwery SimpleX nie mogą zobaczyć Twojego profilu. + + Your profile is stored on your device and only shared with your contacts. + Profil jest udostępniany tylko Twoim kontaktom. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów. + alert message + Your random profile Twój losowy profil No comment provided by engineer. - - Your server - Twój serwer - No comment provided by engineer. - Your server address Twój adres serwera No comment provided by engineer. + + Your servers + Twoje serwery + No comment provided by engineer. + Your settings Twoje ustawienia @@ -7831,6 +8935,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. zaakceptowane połączenie call status + + accepted invitation + chat list item title + admin administrator @@ -7866,6 +8974,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. i %lld innych wydarzeń No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts próby @@ -7904,7 +9016,8 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. blocked by admin zablokowany przez admina - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9132,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. connecting… łączenie… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9187,8 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. default (%@) domyślne (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9315,6 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. błąd No comment provided by engineer. - - event happened - nowe wydarzenie - No comment provided by engineer. - expired wygasły @@ -8376,20 +9485,19 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. moderowany przez %@ marked deleted chat item preview text + + moderator + member role + months miesiące time unit - - mute - wycisz - No comment provided by engineer. - never nigdy - No comment provided by engineer. + delete after time new message @@ -8420,8 +9528,8 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. off wyłączony enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9571,14 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. peer-to-peer No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption kwantowo odporne szyfrowanie e2e @@ -8478,6 +9594,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. otrzymano potwierdzenie… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call odrzucone połączenie @@ -8508,6 +9628,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. usunął cię rcv group event chat item + + requested to connect + chat list item title + saved zapisane @@ -8607,11 +9731,6 @@ ostatnia otrzymana wiadomość: %2$@ nieznany status No comment provided by engineer. - - unmute - wyłącz wyciszenie - No comment provided by engineer. - unprotected niezabezpieczony @@ -8776,7 +9895,7 @@ ostatnia otrzymana wiadomość: %2$@
- +
@@ -8813,7 +9932,7 @@ ostatnia otrzymana wiadomość: %2$@
- +
@@ -8833,9 +9952,36 @@ ostatnia otrzymana wiadomość: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
@@ -8857,7 +10003,7 @@ ostatnia otrzymana wiadomość: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/contents.json b/apps/ios/SimpleX Localizations/pl.xcloc/contents.json index 0074d85662..c79fba1c1e 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/pl.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "pl", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 5f6cbc3b8f..bbb6c7d22a 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -187,23 +187,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Adicionar novo contato**: para criar seu QR Code ou link único para seu contato. - No comment provided by engineer. - **Create link / QR code** for your contact to use. **Crie um link / QR code** para seu contato usar. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Mais privado**: verifique as novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor do SimpleX Chat, mas não quantos contatos ou mensagens você tem. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Mais privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo). No comment provided by engineer. @@ -217,8 +212,8 @@ **Observação**: NÃO será possível recuperar ou alterar a frase secreta se você a perder. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Recomendado**: o token do dispositivo e as notificações são enviados para o servidor de notificações do SimpleX Chat, mas não o conteúdo, o tamanho ou o remetente da mensagem. No comment provided by engineer. @@ -1209,8 +1204,8 @@ Mensagens diretas chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Mensagens diretas entre membros são proibidas neste grupo. No comment provided by engineer. @@ -1229,8 +1224,8 @@ Mensagens temporárias são proibidas nesse chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Mensagens que temporárias são proibidas neste grupo. No comment provided by engineer. @@ -1643,18 +1638,18 @@ Os membros do grupo podem excluir mensagens enviadas de forma irreversível. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Os membros do grupo podem enviar DMs. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Os membros do grupo podem enviar mensagens que desaparecem. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Os membros do grupo podem enviar mensagens de voz. No comment provided by engineer. @@ -1761,8 +1756,8 @@ A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Imune a spam e abuso No comment provided by engineer. @@ -1878,8 +1873,8 @@ A exclusão irreversível de mensagens é proibida neste chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. A exclusão irreversível de mensagens é proibida neste grupo. No comment provided by engineer. @@ -2209,8 +2204,8 @@ We will be adding server redundancy to prevent lost messages. Hosts Onion não serão usados. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2267,8 +2262,8 @@ We will be adding server redundancy to prevent lost messages. Abrir console de chat authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. Protocolo de código aberto – qualquer um pode executar os servidores. No comment provided by engineer. @@ -2306,8 +2301,8 @@ We will be adding server redundancy to prevent lost messages. Cole o link que você recebeu na caixa abaixo para conectar com o seu contato. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. Pessoas podem se conectar com você somente via links compartilhados. No comment provided by engineer. @@ -2961,8 +2956,8 @@ We will be adding server redundancy to prevent lost messages. Thank you for installing SimpleX Chat! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. A 1ª plataforma sem nenhum identificador de usuário – privada por design. No comment provided by engineer. @@ -2998,8 +2993,8 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging A próxima geração de mensageiros privados No comment provided by engineer. @@ -3007,8 +3002,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. O perfil é compartilhado apenas com seus contatos. No comment provided by engineer. @@ -3071,8 +3066,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3274,8 +3269,8 @@ Para se conectar, peça ao seu contato para criar outro link de conexão e verif Mensagens de voz são proibidas neste chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Mensagens de voz são proibidas neste grupo. No comment provided by engineer. @@ -3402,10 +3397,6 @@ Para se conectar, peça ao seu contato para criar outro link de conexão e verif Você pode usar markdown para formatar mensagens: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. Você não pôde ser verificado; por favor, tente novamente. @@ -5434,8 +5425,8 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Advanced settings Configurações avançadas - - All data is private to your device. + + All data is kept private on your device. Toda informação é privada em seu dispositivo. @@ -5482,8 +5473,8 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi (this device v%@) este dispositivo - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Adicionar contato**: criar um novo link de convite ou conectar via um link que você recebeu. @@ -5502,6 +5493,158 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Archived contacts Contatos arquivados + + Cellular + Rede móvel + + + %d file(s) failed to download. + %d arquivo(s) falharam ao ser baixados. + + + %d file(s) were deleted. + %d arquivo(s) foram apagados. + + + %d messages not forwarded + %d mensagens não encaminhadas + + + Bad desktop address + Endereço de desktop incorreto + + + Blur for better privacy. + Borrar para melhor privacidade. + + + %d file(s) are still being downloaded. + %d arquivo(s) ainda estão sendo baixados. + + + %d file(s) were not downloaded. + %d arquivo(s) não foram baixados. + + + Chat colors + Cores do chat + + + %lld group events + %lld eventos do grupo + + + %lld messages moderated by %@ + %lld mensagens moderadas por %@ + + + %1$@, %2$@ + %1$@, %2$@ + + + %lld new interface languages + %lld novos idiomas de interface + + + Better networking + Melhores redes + + + 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 e Ucraniano - obrigado aos usuários e [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + + + Better groups + Melhores grupos + + + Capacity exceeded - recipient did not receive previously sent messages. + Capacidade excedida - o destinatário não recebeu as mensagens enviadas anteriormente. + + + Chat migrated! + Conversa migrada! + + + Auto-accept settings + Aceitar automaticamente configurações + + + App encrypts new local files (except videos). + O aplicativo criptografa novos arquivos locais (exceto videos). + + + App session + Sessão do aplicativo + + + Acknowledged + Reconhecido + + + Acknowledgement errors + Erros conhecidos + + + Chat list + Lista de conversas + + + Chat database exported + Banco de dados da conversa exportado + + + Chat preferences were changed. + As preferências de bate-papo foram alteradas. + + + Chat theme + Tema da conversa + + + Better calls + Chamadas melhores + + + Better user experience + Melhor experiência do usuário + + + Allow downgrade + Permitir redução + + + Additional secondary + Secundária adicional + + + App data migration + Migração de dados do aplicativo + + + Archive and upload + Arquivar e enviar + + + Background + Fundo + + + Better message dates. + Datas de mensagens melhores. + + + Better notifications + Notificações melhores + + + Better security ✅ + Melhor segurança ✅ + + + Chat profile + Perfil da conversa +
diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index a9bf86e778..bc8bf79da1 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -214,22 +214,17 @@ Available in v5.1 ) No comment provided by engineer.
- - **Add new contact**: to create your one-time QR Code or link for your contact. - **Adicionar novo contato**: para criar seu QR Code único ou link para seu contato. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Mais privado**: verifique novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor SimpleX Chat, mas não com quantos contatos ou mensagens você possui. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **Totalmente privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo). No comment provided by engineer. @@ -242,8 +237,8 @@ Available in v5.1 **Atenção**: Você NÃO poderá recuperar ou alterar a senha caso a perca. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Recomendado**: O token do dispositivo e as notificações são enviados ao servidor de notificação do SimpleX Chat, mas não o conteúdo, o tamanho da mensagem ou de quem ela é. No comment provided by engineer. @@ -1232,8 +1227,8 @@ Available in v5.1 Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1248,8 +1243,8 @@ Available in v5.1 Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1684,16 +1679,16 @@ Available in v5.1 Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1812,8 +1807,8 @@ Available in v5.1 Immediately No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1925,8 +1920,8 @@ Available in v5.1 Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2120,8 +2115,8 @@ Available in v5.1 Migration is completed No comment provided by engineer. - - Migrations: %@ + + Migrations: No comment provided by engineer. @@ -2278,8 +2273,8 @@ Available in v5.1 Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2338,8 +2333,8 @@ Available in v5.1 Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2394,8 +2389,8 @@ Available in v5.1 Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -3098,8 +3093,8 @@ Available in v5.1 Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3143,16 +3138,16 @@ 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. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. @@ -3215,8 +3210,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3441,8 +3436,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -3582,10 +3577,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -4302,8 +4293,8 @@ SimpleX servers cannot see your profile. %lld novas interface de idiomas No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Adicionar contato**: para criar um novo link de convite ou conectar-se por meio de um link que você recebeu. No comment provided by engineer. 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 969a7d68e0..419fa75375 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (можно скопировать) @@ -127,6 +100,16 @@ %@ подтверждён No comment provided by engineer. + + %@ server + %@ сервер + No comment provided by engineer. + + + %@ servers + %@ серверы + No comment provided by engineer. + %@ uploaded %@ загружено @@ -137,6 +120,11 @@ %@ хочет соединиться! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ и %lld членов группы @@ -144,7 +132,7 @@ %@, %@ and %lld other members connected - %@, %@ и %lld других членов соединены + установлено соединение с %@, %@ и %lld другими членами группы No comment provided by engineer. @@ -157,11 +145,36 @@ %d дней time interval + + %d file(s) are still being downloaded. + %d файл(ов) загружаются. + forward confirmation reason + + + %d file(s) failed to download. + %d файл(ов) не удалось загрузить. + forward confirmation reason + + + %d file(s) were deleted. + %d файлов было удалено. + forward confirmation reason + + + %d file(s) were not downloaded. + %d файлов не было загружено. + forward confirmation reason + %d hours %d ч. time interval + + %d messages not forwarded + %d сообщений не переслано + alert title + %d min %d мин @@ -177,6 +190,11 @@ %d сек time interval + + %d seconds(s) + %d секунд + delete after time + %d skipped message(s) %d пропущенных сообщение(й) @@ -214,7 +232,7 @@ %lld members - Членов группы: %lld + %lld членов No comment provided by engineer. @@ -247,11 +265,6 @@ %lld новых языков интерфейса No comment provided by engineer. - - %lld second(s) - %lld секунд - No comment provided by engineer. - %lld seconds %lld секунд @@ -302,11 +315,6 @@ %u сообщений пропущено. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (новое) @@ -317,19 +325,9 @@ (это устройство v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Добавить контакт**: создать новую ссылку-приглашение или подключиться через полученную ссылку. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Добавить новый контакт**: чтобы создать одноразовый QR код или ссылку для Вашего контакта. + + **Create 1-time link**: to create and share a new invitation link. + **Добавить контакт**: создать и поделиться новой ссылкой-приглашением. No comment provided by engineer. @@ -337,14 +335,14 @@ **Создать группу**: создать новую группу. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **Более конфиденциально**: проверять новые сообщения каждые 20 минут. Токен устройства будет отправлен на сервер уведомлений SimpleX Chat, но у сервера не будет информации о количестве контактов и сообщений. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. + **Более конфиденциально**: проверять новые сообщения каждые 20 минут. Только токен устройства будет отправлен на сервер уведомлений SimpleX Chat, но у сервера не будет информации о количестве контактов и какой либо информации о сообщениях. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). - **Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. + **Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat. Сообщения проверяются в фоновом режиме, когда система позволяет, в зависимости от того, как часто Вы используете приложение. No comment provided by engineer. @@ -357,11 +355,16 @@ **Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они. No comment provided by engineer. + + **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. @@ -387,11 +390,6 @@ \*жирный* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +426,6 @@ - история редактирования. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 сек @@ -446,7 +439,8 @@ 1 day 1 день - time interval + delete after time +time interval 1 hour @@ -461,12 +455,29 @@ 1 month 1 месяц - time interval + delete after time +time interval 1 week 1 неделю - time interval + delete after time +time interval + + + 1 year + 1 год + delete after time + + + 1-time link + Одноразовая ссылка + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Одноразовая ссылка может быть использована *только с одним контактом* - поделитесь при встрече или через любой мессенджер. + No comment provided by engineer. 5 minutes @@ -483,11 +494,6 @@ 30 секунд No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -518,8 +524,8 @@ 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-соединение (и авторизация SOCKS) будет использоваться **для каждого контакта и члена группы**. -**Обратите внимание**: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать. + Будет использовано отдельное TCP соединение **для каждого контакта и члена группы**. +**Примечание**: Чем больше подключений, тем быстрее разряжается батарея и расходуется трафик, а некоторые соединения могут отваливаться. No comment provided by engineer. @@ -537,19 +543,14 @@ Прекратить изменение адреса? No comment provided by engineer. - - About SimpleX - О SimpleX - No comment provided by engineer. - About SimpleX Chat Информация о SimpleX Chat No comment provided by engineer. - - About SimpleX address - Об адресе SimpleX + + About operators + Об операторах No comment provided by engineer. @@ -561,8 +562,13 @@ Accept Принять accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Принять условия + No comment provided by engineer. Accept connection request? @@ -578,7 +584,12 @@ Accept incognito Принять инкогнито accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Принятые условия + No comment provided by engineer. Acknowledged @@ -590,6 +601,11 @@ Ошибки подтверждения No comment provided by engineer. + + Active + Активный + token status text + Active connections Активные соединения @@ -600,14 +616,14 @@ Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. No comment provided by engineer. - - Add contact - Добавить контакт + + Add friends + Добавить друзей No comment provided by engineer. - - Add preset servers - Добавить серверы по умолчанию + + Add list + Добавить список No comment provided by engineer. @@ -625,16 +641,41 @@ Добавить серверы через QR код. No comment provided by engineer. + + Add team members + Добавить сотрудников + No comment provided by engineer. + Add to another device Добавить на другое устройство No comment provided by engineer. + + Add to list + Добавить в список + No comment provided by engineer. + Add welcome message Добавить приветственное сообщение No comment provided by engineer. + + Add your team members to the conversations. + Добавьте сотрудников в разговор. + No comment provided by engineer. + + + Added media & file servers + Дополнительные серверы файлов и медиа + No comment provided by engineer. + + + Added message servers + Дополнительные серверы сообщений + No comment provided by engineer. + Additional accent Дополнительный акцент @@ -660,6 +701,16 @@ Изменение адреса будет прекращено. Будет использоваться старый адрес. No comment provided by engineer. + + Address or 1-time link? + Адрес или одноразовая ссылка? + No comment provided by engineer. + + + Address settings + Настройки адреса + No comment provided by engineer. + Admins can block a member for all. Админы могут заблокировать члена группы. @@ -680,6 +731,11 @@ Настройки сети No comment provided by engineer. + + All + Все + No comment provided by engineer. + All app data is deleted. Все данные приложения будут удалены. @@ -690,19 +746,29 @@ Все чаты и сообщения будут удалены - это нельзя отменить! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + Все чаты будут удалены из списка %@, и список удален. + alert message + All data is erased when it is entered. Все данные удаляются при его вводе. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Все данные хранятся только на вашем устройстве. No comment provided by engineer. All group members will remain connected. - Все члены группы, которые соединились через эту ссылку, останутся в группе. + Все члены группы останутся соединены. + 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. @@ -723,6 +789,15 @@ All profiles Все профили + profile dropdown + + + All reports will be archived for you. + Все сообщения о нарушениях будут заархивированы для вас. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -782,7 +857,7 @@ Allow sending direct messages to members. - Разрешить посылать прямые сообщения членам группы. + Разрешить личные сообщения членам группы. No comment provided by engineer. @@ -800,6 +875,11 @@ Разрешить необратимо удалять отправленные сообщения. (24 часа) No comment provided by engineer. + + Allow to report messsages to moderators. + Разрешить отправлять сообщения о нарушениях модераторам. + No comment provided by engineer. + Allow to send SimpleX links. Разрешить отправлять ссылки SimpleX. @@ -880,11 +960,21 @@ Будет создан пустой профиль чата с указанным именем, и приложение откроется в обычном режиме. No comment provided by engineer. + + Another reason + Другая причина + report reason + Answer call Принять звонок No comment provided by engineer. + + Anybody can host servers. + Кто угодно может запустить сервер. + No comment provided by engineer. + App build: %@ Сборка приложения: %@ @@ -900,6 +990,11 @@ Приложение шифрует новые локальные файлы (кроме видео). No comment provided by engineer. + + App group: + Группа приложения: + No comment provided by engineer. + App icon Иконка @@ -915,6 +1010,11 @@ Код доступа в приложение будет заменен кодом самоуничтожения. No comment provided by engineer. + + App session + Сессия приложения + No comment provided by engineer. + App version Версия приложения @@ -940,6 +1040,21 @@ Применить к No comment provided by engineer. + + Archive + Архивировать + No comment provided by engineer. + + + Archive %lld reports? + Архивировать %lld сообщений о нарушениях? + No comment provided by engineer. + + + Archive all reports? + Архивировать все сообщения о нарушениях? + No comment provided by engineer. + Archive and upload Архивировать и загрузить @@ -950,6 +1065,21 @@ Архивируйте контакты чтобы продолжить переписку. No comment provided by engineer. + + Archive report + Архивировать сообщение о нарушении + No comment provided by engineer. + + + Archive report? + Архивировать сообщение о нарушении? + No comment provided by engineer. + + + Archive reports + Архивировать сообщения о нарушениях + swipe action + Archived contacts Архивированные контакты @@ -1020,6 +1150,11 @@ Автоприем изображений No comment provided by engineer. + + Auto-accept settings + Настройки автоприема + alert title + Back Назад @@ -1045,11 +1180,26 @@ Ошибка хэш сообщения No comment provided by engineer. + + Better calls + Улучшенные звонки + No comment provided by engineer. + Better groups Улучшенные группы No comment provided by engineer. + + Better groups performance + Улучшенная производительность групп + No comment provided by engineer. + + + Better message dates. + Улучшенные даты сообщений. + No comment provided by engineer. + Better messages Улучшенные сообщения @@ -1060,6 +1210,26 @@ Улучшенные сетевые функции No comment provided by engineer. + + Better notifications + Улучшенные уведомления + No comment provided by engineer. + + + Better privacy and security + Улучшенная конфиденциальность и безопасность + No comment provided by engineer. + + + Better security ✅ + Улучшенная безопасность ✅ + No comment provided by engineer. + + + Better user experience + Улучшенный интерфейс + No comment provided by engineer. + Black Черная @@ -1077,7 +1247,7 @@ Block group members - Блокируйте членов группы + Заблокировать членов группы No comment provided by engineer. @@ -1087,7 +1257,7 @@ Block member for all? - Заблокировать члена для всех? + Заблокировать для всех? No comment provided by engineer. @@ -1140,11 +1310,35 @@ Болгарский, финский, тайский и украинский - благодаря пользователям и [Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Бизнес адрес + No comment provided by engineer. + + + Business chats + Бизнес разговоры + No comment provided by engineer. + + + Businesses + Бизнесы + 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: +- send only legal content in public groups. +- respect other users – no spam. + Используя SimpleX Chat, Вы согласны: +- отправлять только законные сообщения в публичных группах. +- уважать других пользователей – не отправлять спам. + No comment provided by engineer. + Call already ended! Звонок уже завершен! @@ -1172,7 +1366,7 @@ Can't call member - Не удается позвонить члену группы + Не удаётся позвонить члену группы No comment provided by engineer. @@ -1187,13 +1381,14 @@ Can't message member - Не удается написать члену группы + Не удаётся отправить сообщение члену группы No comment provided by engineer. Cancel Отменить - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1408,7 @@ Cannot receive file Невозможно получить файл - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1425,16 @@ Поменять No comment provided by engineer. + + Change automatic message deletion? + Измененить автоматическое удаление сообщений? + alert title + + + Change chat profiles + Поменять профили + authentication reason + Change database passphrase? Поменять пароль базы данных? @@ -1274,11 +1479,21 @@ Change self-destruct passcode Изменить код самоуничтожения authentication reason - set passcode view +set passcode view - - Chat archive - Архив чата + + Chat + Разговор + No comment provided by engineer. + + + Chat already exists + Разговор уже существует + No comment provided by engineer. + + + Chat already exists! + Разговор уже существует! No comment provided by engineer. @@ -1341,20 +1556,50 @@ Предпочтения No comment provided by engineer. + + Chat preferences were changed. + Настройки чата были изменены. + alert message + + + Chat profile + Профиль чата + No comment provided by engineer. + Chat theme Тема чата No comment provided by engineer. + + 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. + Chats Чаты No comment provided by engineer. + + Check messages every 20 min. + Проверять сообщения каждые 20 минут. + No comment provided by engineer. + + + Check messages when allowed. + Проверять сообщения по возможности. + No comment provided by engineer. + Check server address and try again. Проверьте адрес сервера и попробуйте снова. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1651,16 @@ Очистить разговор? No comment provided by engineer. + + Clear group? + Очистить группу? + No comment provided by engineer. + + + Clear or delete group? + Очистить или удалить группу? + No comment provided by engineer. + Clear private notes? Очистить личные заметки? @@ -1426,6 +1681,11 @@ Режим цветов No comment provided by engineer. + + Community guidelines violation + Нарушение правил группы + report reason + Compare file Сравнение файла @@ -1441,14 +1701,49 @@ Готово No comment provided by engineer. + + Conditions accepted on: %@. + Условия приняты: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Условия приняты для оператора(ов): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Условия уже приняты для следующих оператора(ов): **%@**. + No comment provided by engineer. + + + Conditions of use + Условия использования + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Условия будут приняты для оператора(ов): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Условия будут приняты: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Условия будут автоматически приняты для включенных операторов: %@. + No comment provided by engineer. + Configure ICE servers Настройка ICE серверов No comment provided by engineer. - - Configured %@ servers - Настроенные %@ серверы + + Configure server operators + Настроить операторов серверов No comment provided by engineer. @@ -1501,6 +1796,11 @@ Подтвердить загрузку No comment provided by engineer. + + Confirmed + Подтвержденный + token status text + Connect Соединиться @@ -1620,6 +1920,11 @@ This is your own one-time link! Состояние соединения и серверов. No comment provided by engineer. + + Connection blocked + Соединение заблокировано + No comment provided by engineer. + Connection error Ошибка соединения @@ -1630,6 +1935,18 @@ This is your own one-time link! Ошибка соединения (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + Соединение заблокировано сервером оператора: +%@ + No comment provided by engineer. + + + Connection not ready. + Соединение не готово. + No comment provided by engineer. + Connection notifications Уведомления по соединениям @@ -1640,6 +1957,16 @@ This is your own one-time link! Запрос на соединение отправлен! No comment provided by engineer. + + Connection requires encryption renegotiation. + Соединение требует повторного согласования шифрования. + No comment provided by engineer. + + + Connection security + Безопасность соединения + No comment provided by engineer. + Connection terminated Подключение прервано @@ -1715,6 +2042,11 @@ This is your own one-time link! Контакты могут помечать сообщения для удаления; Вы сможете просмотреть их. No comment provided by engineer. + + Content violates conditions of use + Содержание нарушает условия использования + blocking reason + Continue Продолжить @@ -1740,6 +2072,11 @@ This is your own one-time link! Версия ядра: v%@ No comment provided by engineer. + + Corner + Угол + No comment provided by engineer. + Correct name to %@? Исправить имя на %@? @@ -1750,6 +2087,11 @@ This is your own one-time link! Создать No comment provided by engineer. + + Create 1-time link + Создать одноразовую ссылку + No comment provided by engineer. + Create SimpleX address Создать адрес SimpleX @@ -1760,11 +2102,6 @@ This is your own one-time link! Создайте группу, используя случайный профиль. No comment provided by engineer. - - Create an address to let people connect with you. - Создайте адрес, чтобы можно было соединиться с вами. - No comment provided by engineer. - Create file Создание файла @@ -1785,6 +2122,11 @@ This is your own one-time link! Создать ссылку No comment provided by engineer. + + Create list + Создать список + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Создайте новый профиль в [приложении для компьютера](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2167,6 @@ This is your own one-time link! Создано: %@ copied message info - - Created on %@ - Дата создания %@ - No comment provided by engineer. - Creating archive link Создание ссылки на архив @@ -1845,6 +2182,11 @@ This is your own one-time link! Текущий Код No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Текст условий использования не может быть показан, вы можете посмотреть их через ссылку: + No comment provided by engineer. + Current passphrase… Текущий пароль… @@ -1865,6 +2207,11 @@ This is your own one-time link! Пользовательское время No comment provided by engineer. + + Customizable message shape. + Настраиваемая форма сообщений. + No comment provided by engineer. + Customize theme Настроить тему @@ -1996,8 +2343,8 @@ This is your own one-time link! Delete Удалить - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2381,14 @@ This is your own one-time link! Удалить и уведомить контакт No comment provided by engineer. - - Delete archive - Удалить архив + + Delete chat + Удалить разговор No comment provided by engineer. - - Delete chat archive? - Удалить архив чата? + + Delete chat messages from your device. + Удалить сообщения с вашего устройства. No comment provided by engineer. @@ -2054,6 +2401,11 @@ This is your own one-time link! Удалить профиль? No comment provided by engineer. + + Delete chat? + Удалить разговор? + No comment provided by engineer. + Delete connection Удалить соединение @@ -2129,6 +2481,11 @@ This is your own one-time link! Удалить ссылку? No comment provided by engineer. + + Delete list? + Удалить список? + alert title + Delete member message? Удалить сообщение участника? @@ -2142,7 +2499,7 @@ This is your own one-time link! Delete messages Удалить сообщения - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2516,11 @@ This is your own one-time link! Удалить предыдущую версию данных? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Удаляйте или модерируйте до 200 сообщений. + No comment provided by engineer. + Delete pending connection? Удалить ожидаемое соединение? @@ -2174,6 +2536,11 @@ This is your own one-time link! Удаление очереди server test step + + Delete report + Удалить сообщение о нарушении + No comment provided by engineer. + Delete up to 20 messages at once. Удаляйте до 20 сообщений за раз. @@ -2209,6 +2576,11 @@ This is your own one-time link! Ошибки удаления No comment provided by engineer. + + Delivered even when Apple drops them. + Доставляются даже тогда, когда Apple их теряет. + No comment provided by engineer. + Delivery Доставка @@ -2309,9 +2681,13 @@ This is your own one-time link! Прямые сообщения chat feature - - Direct messages between members are prohibited in this group. - Прямые сообщения между членами группы запрещены. + + 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. @@ -2324,6 +2700,16 @@ This is your own one-time link! Отключить блокировку SimpleX authentication reason + + Disable automatic message deletion? + Отключить автоматическое удаление сообщений? + alert title + + + Disable delete messages + Отключить удаление сообщений + alert button + Disable for all Выключить для всех @@ -2349,8 +2735,8 @@ This is your own one-time link! Исчезающие сообщения запрещены в этом чате. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Исчезающие сообщения запрещены в этой группе. No comment provided by engineer. @@ -2406,7 +2792,16 @@ This is your own one-time link! Do not send history to new members. - Не отправлять историю новым членам. + No comment provided by engineer. + + + Do not use credentials with proxy. + Не использовать учетные данные с прокси. + No comment provided by engineer. + + + Documents: + Документы: No comment provided by engineer. @@ -2419,11 +2814,21 @@ This is your own one-time link! Не включать No comment provided by engineer. + + Don't miss important messages. + Не пропустите важные сообщения. + No comment provided by engineer. + Don't show again Не показывать No comment provided by engineer. + + Done + Готово + No comment provided by engineer. + Downgrade and open chat Откатить версию и открыть чат @@ -2432,7 +2837,8 @@ This is your own one-time link! Download Загрузить - chat item action + alert button +chat item action Download errors @@ -2449,6 +2855,11 @@ This is your own one-time link! Загрузка файла server test step + + Download files + Загрузить файлы + alert action + Downloaded Принято @@ -2479,6 +2890,11 @@ This is your own one-time link! Длительность No comment provided by engineer. + + E2E encrypted notifications. + E2E зашифрованные нотификации. + No comment provided by engineer. + Edit Редактировать @@ -2499,6 +2915,10 @@ This is your own one-time link! Включить (кроме исключений) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Включить блокировку SimpleX @@ -2512,7 +2932,7 @@ This is your own one-time link! Enable automatic message deletion? Включить автоматическое удаление сообщений? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3059,11 @@ This is your own one-time link! Ошибка нового соглашения о шифровании. No comment provided by engineer. + + Encryption renegotiation in progress. + Выполняется повторное согласование шифрования. + No comment provided by engineer. + Enter Passcode Введите Код @@ -2704,26 +3129,35 @@ This is your own one-time link! Ошибка при прекращении изменения адреса No comment provided by engineer. + + Error accepting conditions + Ошибка приема условий + alert title + Error accepting contact request Ошибка при принятии запроса на соединение No comment provided by engineer. - - Error accessing database file - Ошибка при доступе к данным чата - No comment provided by engineer. - Error adding member(s) - Ошибка при добавлении членов группы No comment provided by engineer. + + Error adding server + Ошибка добавления сервера + alert title + Error changing address Ошибка при изменении адреса No comment provided by engineer. + + Error changing connection profile + Ошибка при изменении профиля соединения + No comment provided by engineer. + Error changing role Ошибка при изменении роли @@ -2734,6 +3168,16 @@ This is your own one-time link! Ошибка при изменении настройки No comment provided by engineer. + + Error changing to incognito! + Ошибка при смене на Инкогнито! + No comment provided by engineer. + + + Error checking token status + Ошибка проверки статуса токена + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Ошибка подключения к пересылающему серверу %@. Попробуйте позже. @@ -2754,9 +3198,13 @@ This is your own one-time link! Ошибка при создании ссылки группы No comment provided by engineer. + + Error creating list + Ошибка создания списка + alert title + Error creating member contact - Ошибка создания контакта с членом группы No comment provided by engineer. @@ -2769,6 +3217,11 @@ This is your own one-time link! Ошибка создания профиля! No comment provided by engineer. + + Error creating report + Ошибка создания сообщения о нарушении + No comment provided by engineer. + Error decrypting file Ошибка расшифровки файла @@ -2849,9 +3302,14 @@ This is your own one-time link! Ошибка при вступлении в группу No comment provided by engineer. - - Error loading %@ servers - Ошибка загрузки %@ серверов + + Error loading servers + Ошибка загрузки серверов + alert title + + + Error migrating settings + Ошибка миграции настроек No comment provided by engineer. @@ -2862,7 +3320,7 @@ This is your own one-time link! Error receiving file Ошибка при получении файла - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3332,35 @@ This is your own one-time link! Ошибка переподключения к серверам No comment provided by engineer. + + Error registering for notifications + Ошибка регистрации для уведомлений + alert title + Error removing member - Ошибка при удалении члена группы No comment provided by engineer. + + Error reordering lists + Ошибка сортировки списков + alert title + Error resetting statistics Ошибка сброса статистики No comment provided by engineer. - - Error saving %@ servers - Ошибка при сохранении %@ серверов - No comment provided by engineer. - Error saving ICE servers Ошибка при сохранении ICE серверов No comment provided by engineer. + + Error saving chat list + Ошибка сохранения списка чатов + alert title + Error saving group profile Ошибка при сохранении профиля группы @@ -2909,6 +3376,11 @@ This is your own one-time link! Ошибка сохранения пароля в Keychain No comment provided by engineer. + + Error saving servers + Ошибка сохранения серверов + alert title + Error saving settings Ошибка сохранения настроек @@ -2931,7 +3403,6 @@ This is your own one-time link! Error sending member contact invitation - Ошибка отправки приглашения члену группы No comment provided by engineer. @@ -2954,16 +3425,26 @@ This is your own one-time link! Ошибка при остановке чата No comment provided by engineer. + + Error switching profile + Ошибка переключения профиля + No comment provided by engineer. + Error switching profile! Ошибка выбора профиля! - No comment provided by engineer. + alertTitle Error synchronizing connection Ошибка синхронизации соединения No comment provided by engineer. + + Error testing server connection + Ошибка проверки соединения с сервером + No comment provided by engineer. + Error updating group link Ошибка обновления ссылки группы @@ -2974,6 +3455,11 @@ This is your own one-time link! Ошибка при обновлении сообщения No comment provided by engineer. + + Error updating server + Ошибка сохранения сервера + alert title + Error updating settings Ошибка при сохранении настроек сети @@ -3002,8 +3488,9 @@ This is your own one-time link! Error: %@ Ошибка: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3507,11 @@ This is your own one-time link! Ошибки No comment provided by engineer. + + Errors in servers configuration. + Ошибки в настройках серверов. + servers error + Even when disabled in the conversation. Даже когда они выключены в разговоре. @@ -3035,6 +3527,11 @@ This is your own one-time link! Раскрыть chat item action + + Expired + Истекший + token status text + Export database Экспорт архива чата @@ -3075,20 +3572,49 @@ This is your own one-time link! Быстрые и не нужно ждать, когда отправитель онлайн! No comment provided by engineer. + + Faster deletion of groups. + Ускорено удаление групп. + No comment provided by engineer. + Faster joining and more reliable messages. Быстрое вступление и надежная доставка сообщений. No comment provided by engineer. + + Faster sending messages. + Ускорена отправка сообщений. + No comment provided by engineer. + Favorite Избранный swipe action + + Favorites + Избранное + No comment provided by engineer. + File error Ошибка файла - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Ошибки файлов: +%@ + alert message + + + File is blocked by server operator: +%@. + Файл заблокирован оператором сервера: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,8 +3671,8 @@ This is your own one-time link! Файлы и медиа chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Файлы и медиа запрещены в этой группе. No comment provided by engineer. @@ -3212,24 +3738,73 @@ This is your own one-time link! Fix not supported by group member - Починка не поддерживается членом группы No comment provided by engineer. + + For all moderators + Для всех модераторов + No comment provided by engineer. + + + For chat profile %@: + Для профиля чата %@: + servers error + For console Для консоли No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Например, если Ваш контакт получает сообщения через сервер SimpleX Chat, Ваше приложение доставит их через сервер Flux. + No comment provided by engineer. + + + For me + Для меня + No comment provided by engineer. + + + For private routing + Для доставки сообщений + No comment provided by engineer. + + + For social media + Для социальных сетей + No comment provided by engineer. + Forward Переслать chat item action + + Forward %d message(s)? + Переслать %d сообщение(й)? + alert title + Forward and save messages Переслать и сохранить сообщение No comment provided by engineer. + + Forward messages + Переслать сообщения + alert action + + + Forward messages without files? + Переслать сообщения без файлов? + alert message + + + Forward up to 20 messages at once. + Пересылайте до 20 сообщений за раз. + No comment provided by engineer. + Forwarded Переслано @@ -3240,6 +3815,11 @@ This is your own one-time link! Переслано из No comment provided by engineer. + + Forwarding %lld messages + Пересылка %lld сообщений + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Пересылающий сервер %@ не смог подключиться к серверу назначения %@. Попробуйте позже. @@ -3289,14 +3869,8 @@ Error: %2$@ Полное имя (не обязательно) No comment provided by engineer. - - Full name: - Полное имя: - No comment provided by engineer. - Fully decentralized – visible only to members. - Группа полностью децентрализована – она видна только членам. No comment provided by engineer. @@ -3314,6 +3888,11 @@ Error: %2$@ ГИФ файлы и стикеры No comment provided by engineer. + + Get notified when mentioned. + Уведомления, когда Вас упомянули. + No comment provided by engineer. + Good afternoon! Добрый день! @@ -3379,41 +3958,6 @@ Error: %2$@ Ссылки групп No comment provided by engineer. - - Group members can add message reactions. - Члены группы могут добавлять реакции на сообщения. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Члены группы могут необратимо удалять отправленные сообщения. (24 часа) - No comment provided by engineer. - - - Group members can send SimpleX links. - Члены группы могут отправлять ссылки SimpleX. - No comment provided by engineer. - - - Group members can send direct messages. - Члены группы могут посылать прямые сообщения. - No comment provided by engineer. - - - Group members can send disappearing messages. - Члены группы могут посылать исчезающие сообщения. - No comment provided by engineer. - - - Group members can send files and media. - Члены группы могут слать файлы и медиа. - No comment provided by engineer. - - - Group members can send voice messages. - Члены группы могут отправлять голосовые сообщения. - No comment provided by engineer. - Group message: Групповое сообщение: @@ -3436,7 +3980,6 @@ Error: %2$@ Group profile is stored on members' devices, not on the servers. - Профиль группы хранится на устройствах членов, а не на серверах. No comment provided by engineer. @@ -3446,7 +3989,6 @@ Error: %2$@ Group will be deleted for all members - this cannot be undone! - Группа будет удалена для всех членов - это действие нельзя отменить! No comment provided by engineer. @@ -3454,11 +3996,21 @@ Error: %2$@ Группа будет удалена для Вас - это действие нельзя отменить! No comment provided by engineer. + + Groups + Группы + No comment provided by engineer. + Help Помощь No comment provided by engineer. + + Help admins moderating their groups. + Помогайте администраторам модерировать их группы. + No comment provided by engineer. + Hidden Скрытое @@ -3501,7 +4053,6 @@ Error: %2$@ History is not sent to new members. - История не отправляется новым членам. No comment provided by engineer. @@ -3509,10 +4060,20 @@ Error: %2$@ Как SimpleX работает No comment provided by engineer. + + How it affects privacy + Как это влияет на конфиденциальность + No comment provided by engineer. + + + How it helps privacy + Как это улучшает конфиденциальность + No comment provided by engineer. + How it works Как это работает - No comment provided by engineer. + alert button How to @@ -3521,7 +4082,7 @@ Error: %2$@ How to use it - Как использовать + Про адрес No comment provided by engineer. @@ -3539,6 +4100,11 @@ Error: %2$@ ICE серверы (один на строке) No comment provided by engineer. + + IP address + IP адрес + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Если Вы не можете встретиться лично, покажите QR-код во время видеозвонка или поделитесь ссылкой. @@ -3579,8 +4145,8 @@ Error: %2$@ Сразу No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Защищен от спама No comment provided by engineer. @@ -3614,6 +4180,12 @@ Error: %2$@ Импорт архива No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Улучшенная доставка, меньше трафик. + No comment provided by engineer. + Improved message delivery Улучшенная доставка сообщений @@ -3644,6 +4216,16 @@ Error: %2$@ Звуки во время звонков No comment provided by engineer. + + Inappropriate content + Неприемлемый контент + report reason + + + Inappropriate profile + Неприемлемый профиль + report reason + Incognito Инкогнито @@ -3714,6 +4296,11 @@ Error: %2$@ [SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Мгновенно + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4308,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Мгновенно - No comment provided by engineer. - Interface Интерфейс @@ -3736,6 +4318,31 @@ Error: %2$@ Цвета интерфейса No comment provided by engineer. + + Invalid + Недействительный + token status text + + + Invalid (bad token) + Недействительный (плохой токен) + token status text + + + Invalid (expired) + Недействительный (истекший) + token status text + + + Invalid (unregistered) + Недействительный (незарегистрированный) + token status text + + + Invalid (wrong topic) + Недействительный (плохой заголовок) + token status text + Invalid QR code Неверный QR код @@ -3774,7 +4381,7 @@ Error: %2$@ Invalid server address! Ошибка в адресе сервера! - No comment provided by engineer. + alert title Invalid status @@ -3793,7 +4400,11 @@ Error: %2$@ Invite members - Пригласить членов группы + No comment provided by engineer. + + + Invite to chat + Пригласить в разговор No comment provided by engineer. @@ -3811,8 +4422,8 @@ Error: %2$@ Необратимое удаление сообщений запрещено в этом чате. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Необратимое удаление сообщений запрещено в этой группе. No comment provided by engineer. @@ -3902,7 +4513,7 @@ This is your link for group %@! Keep Оставить - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4528,7 @@ This is your link for group %@! Keep unused invitation? Оставить неиспользованное приглашение? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4565,16 @@ This is your link for group %@! Выйти swipe action + + Leave chat + Покинуть разговор + No comment provided by engineer. + + + Leave chat? + Покинуть разговор? + No comment provided by engineer. + Leave group Выйти из группы @@ -3994,6 +4615,21 @@ This is your link for group %@! Связанные компьютеры No comment provided by engineer. + + List + Список + swipe action + + + List name and emoji should be different for all lists. + Название списка и эмодзи должны быть разными для всех списков. + No comment provided by engineer. + + + List name... + Имя списка... + No comment provided by engineer. + Live message! Живое сообщение! @@ -4004,11 +4640,6 @@ This is your link for group %@! "Живые" сообщения No comment provided by engineer. - - Local - Локальные - No comment provided by engineer. - Local name Локальное имя @@ -4029,11 +4660,6 @@ This is your link for group %@! Режим блокировки No comment provided by engineer. - - Make a private connection - Добавьте контакт - No comment provided by engineer. - Make one message disappear Одно исчезающее сообщение @@ -4044,21 +4670,11 @@ This is your link for group %@! Сделайте профиль скрытым! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Пожалуйста, проверьте, что адреса %@ серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?* - No comment provided by engineer. - Mark deleted for everyone Пометить как удаленное для всех @@ -4096,27 +4712,72 @@ This is your link for group %@! Member - Член группы No comment provided by engineer. Member inactive - Член неактивен item status text + + Member reports + Сообщения о нарушениях + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Роль участника будет изменена на "%@". Все участники разговора получат уведомление. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. - Роль члена группы будет изменена на "%@". Все члены группы получат сообщение. No comment provided by engineer. Member role will be changed to "%@". The member will receive a new invitation. - Роль члена группы будет изменена на "%@". Будет отправлено новое приглашение. + No comment provided by engineer. + + + Member will be removed from chat - this cannot be undone! No comment provided by engineer. Member will be removed from group - this cannot be undone! - Член группы будет удален - это действие нельзя отменить! + No comment provided by engineer. + + + Members can add message reactions. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + No comment provided by engineer. + + + Members can send disappearing messages. + No comment provided by engineer. + + + Members can send files and media. + No comment provided by engineer. + + + Members can send voice messages. + No comment provided by engineer. + + + Mention members 👋 No comment provided by engineer. @@ -4151,7 +4812,6 @@ This is your link for group %@! Message may be delivered later if member becomes active. - Сообщение может быть доставлено позже, если член группы станет активным. item status description @@ -4169,8 +4829,8 @@ This is your link for group %@! Реакции на сообщения в этом чате запрещены. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Реакции на сообщения запрещены в этой группе. No comment provided by engineer. @@ -4184,6 +4844,11 @@ This is your link for group %@! Серверы сообщений No comment provided by engineer. + + Message shape + Форма сообщений + No comment provided by engineer. + Message source remains private. Источник сообщения остаётся конфиденциальным. @@ -4224,6 +4889,11 @@ This is your link for group %@! Сообщения от %@ будут показаны! No comment provided by engineer. + + Messages in this chat will never be deleted. + Сообщения в этом чате никогда не будут удалены. + alert message + Messages received Получено сообщений @@ -4234,6 +4904,11 @@ This is your link for group %@! Сообщений отправлено No comment provided by engineer. + + Messages were deleted after you selected them. + Сообщения были удалены после того, как вы их выбрали. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. @@ -4299,9 +4974,9 @@ This is your link for group %@! Перемещение данных завершено No comment provided by engineer. - - Migrations: %@ - Миграции: %@ + + Migrations: + Миграции: No comment provided by engineer. @@ -4319,6 +4994,11 @@ This is your link for group %@! Модерировано: %@ copied message info + + More + Больше + swipe action + More improvements are coming soon! Дополнительные улучшения скоро! @@ -4329,6 +5009,11 @@ This is your link for group %@! Более надежное соединение с сетью. No comment provided by engineer. + + More reliable notifications + Более надежные уведомления + No comment provided by engineer. + Most likely this connection is deleted. Скорее всего, соединение удалено. @@ -4342,7 +5027,12 @@ This is your link for group %@! Mute Без звука - swipe action + notification label action + + + Mute all + Все без звука + notification label action Muted when inactive! @@ -4364,6 +5054,11 @@ This is your link for group %@! Интернет-соединение No comment provided by engineer. + + Network decentralization + Децентрализация сети + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Ошибка сети - сообщение не было отправлено после многократных попыток. @@ -4374,6 +5069,11 @@ This is your link for group %@! Статус сети No comment provided by engineer. + + Network operator + Оператор сети + No comment provided by engineer. + Network settings Настройки сети @@ -4384,11 +5084,26 @@ This is your link for group %@! Состояние сети No comment provided by engineer. + + New + Новый + token status text + New Passcode Новый Код No comment provided by engineer. + + 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 будут использоваться для каждого сервера. + No comment provided by engineer. + New chat Новый чат @@ -4409,11 +5124,6 @@ This is your link for group %@! Новый контакт: notification - - New database archive - Новый архив чата - No comment provided by engineer. - New desktop app! Приложение для компьютера! @@ -4424,6 +5134,11 @@ This is your link for group %@! Новое имя No comment provided by engineer. + + New events + Новые события + notification + New in %@ Новое в %@ @@ -4436,7 +5151,6 @@ This is your link for group %@! New member role - Роль члена группы No comment provided by engineer. @@ -4449,6 +5163,11 @@ This is your link for group %@! Новый пароль… No comment provided by engineer. + + New server + Новый сервер + No comment provided by engineer. + No Нет @@ -4459,6 +5178,21 @@ This is your link for group %@! Нет кода доступа Authentication unavailable + + No chats + Нет чатов + No comment provided by engineer. + + + No chats found + Чаты не найдены + No comment provided by engineer. + + + No chats in list %@ + Нет чатов в списке %@ + No comment provided by engineer. + No contacts selected Контакты не выбраны @@ -4504,31 +5238,106 @@ This is your link for group %@! Нет информации, попробуйте перезагрузить No comment provided by engineer. + + No media & file servers. + Нет серверов файлов и медиа. + servers error + + + No message + Нет сообщения + No comment provided by engineer. + + + No message servers. + Нет серверов сообщений. + servers error + No network connection Нет интернет-соединения No comment provided by engineer. + + No permission to record speech + Нет разрешения на запись речи + No comment provided by engineer. + + + No permission to record video + Нет разрешения на запись видео + No comment provided by engineer. + No permission to record voice message Нет разрешения для записи голосового сообщения No comment provided by engineer. + + No push server + Без сервера нотификаций + No comment provided by engineer. + No received or sent files Нет полученных или отправленных файлов No comment provided by engineer. + + No servers for private message routing. + Нет серверов для доставки сообщений. + servers error + + + No servers to receive files. + Нет серверов для приема файлов. + servers error + + + No servers to receive messages. + Нет серверов для приема сообщений. + servers error + + + No servers to send files. + Нет серверов для отправки файлов. + servers error + + + No token! + Нет токена! + alert title + + + No unread chats + Нет непрочитанных чатов + No comment provided by engineer. + + + No user identifiers. + Без идентификаторов пользователей. + No comment provided by engineer. + Not compatible! Несовместимая версия! No comment provided by engineer. + + Notes + Заметки + No comment provided by engineer. + Nothing selected Ничего не выбрано No comment provided by engineer. + + Nothing to forward! + Нет сообщений, которые можно переслать! + alert title + Notifications Уведомления @@ -4539,13 +5348,25 @@ This is your link for group %@! Уведомления выключены No comment provided by engineer. + + Notifications error + Ошибка уведомлений + alert title + + + Notifications privacy + Конфиденциальность уведомлений + No comment provided by engineer. + + + Notifications status + Статус уведомлений + alert title + Now admins can: - delete members' messages. - disable members ("observer" role) - Теперь админы могут: -- удалять сообщения членов. -- приостанавливать членов (роль "наблюдатель") No comment provided by engineer. @@ -4561,18 +5382,13 @@ This is your link for group %@! Ok Ок - No comment provided by engineer. + alert button Old database Предыдущая версия данных чата No comment provided by engineer. - - Old database archive - Старый архив чата - No comment provided by engineer. - One-time invitation link Одноразовая ссылка @@ -4597,9 +5413,14 @@ Requires compatible VPN. Onion хосты не используются. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**. + + Only chat owners can change preferences. + Только владельцы разговора могут поменять предпочтения. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. + Только пользовательские устройства хранят контакты, группы и сообщения. No comment provided by engineer. @@ -4622,6 +5443,16 @@ Requires compatible VPN. Только владельцы группы могут разрешить голосовые сообщения. No comment provided by engineer. + + Only sender and moderators see it + Только отправитель и модераторы видят это + No comment provided by engineer. + + + Only you and moderators see it + Только вы и модераторы видят это + No comment provided by engineer. + Only you can add message reactions. Только Вы можете добавлять реакции на сообщения. @@ -4675,13 +5506,18 @@ Requires compatible VPN. Open Открыть - No comment provided by engineer. + alert action Open Settings Открыть Настройки No comment provided by engineer. + + Open changes + Открыть изменения + No comment provided by engineer. + Open chat Открыть чат @@ -4692,36 +5528,45 @@ Requires compatible VPN. Открыть консоль authentication reason + + Open conditions + Открыть условия + No comment provided by engineer. + Open group Открыть группу No comment provided by engineer. + + Open link? + alert title + Open migration to another device Открытие миграции на другое устройство authentication reason - - Open server settings - Открыть настройки серверов - No comment provided by engineer. - - - Open user profiles - Открыть профили пользователя - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Открытый протокол и код - кто угодно может запустить сервер. - No comment provided by engineer. - Opening app… Приложение отрывается… No comment provided by engineer. + + Operator + Оператор + No comment provided by engineer. + + + Operator server + Сервер оператора + alert title + + + Or import archive file + Или импортировать файл архива + No comment provided by engineer. + Or paste archive link Или вставьте ссылку архива @@ -4742,15 +5587,27 @@ Requires compatible VPN. Или покажите этот код No comment provided by engineer. + + Or to share privately + Или поделиться конфиденциально + No comment provided by engineer. + + + Organize chats into lists + Организуйте чаты в списки + No comment provided by engineer. + Other Другaя сеть No comment provided by engineer. - - Other %@ servers - Другие %@ серверы - No comment provided by engineer. + + Other file errors: +%@ + Другие ошибки файлов: +%@ + alert message PING count @@ -4787,6 +5644,11 @@ Requires compatible VPN. Код доступа установлен! No comment provided by engineer. + + Password + Пароль + No comment provided by engineer. + Password to show Пароль чтобы раскрыть @@ -4794,7 +5656,6 @@ Requires compatible VPN. Past member %@ - Бывший член %@ past/unknown group member @@ -4819,16 +5680,11 @@ Requires compatible VPN. Pending - В ожидании + Ожидает No comment provided by engineer. - - People can connect to you only via the links you share. - С Вами можно соединиться только через созданные Вами ссылки. - No comment provided by engineer. - - - Periodically + + Periodic Периодически No comment provided by engineer. @@ -4931,11 +5787,31 @@ Error: %@ Пожалуйста, надежно сохраните пароль, Вы НЕ сможете его поменять, если потеряете. No comment provided by engineer. + + Please try to disable and re-enable notfications. + Попробуйте выключить и снова включить уведомления. + token info + + + Please wait for token activation to complete. + Пожалуйста, дождитесь завершения активации токена. + token info + + + Please wait for token to be registered. + Пожалуйста, дождитесь регистрации токена. + token info + Polish interface Польский интерфейс No comment provided by engineer. + + Port + Порт + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Возможно, хэш сертификата в адресе сервера неверный @@ -4946,16 +5822,16 @@ Error: %@ Сохранить последний черновик, вместе с вложениями. No comment provided by engineer. - - Preset server - Сервер по умолчанию - No comment provided by engineer. - Preset server address Адрес сервера по умолчанию No comment provided by engineer. + + Preset servers + Серверы по умолчанию + No comment provided by engineer. + Preview Просмотр @@ -4971,16 +5847,36 @@ Error: %@ Конфиденциальность No comment provided by engineer. + + Privacy for your customers. + Конфиденциальность для ваших покупателей. + 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 Защищенные имена файлов No comment provided by engineer. + + Private media file names. + Конфиденциальные названия медиафайлов. + No comment provided by engineer. + Private message routing Конфиденциальная доставка сообщений @@ -5021,16 +5917,6 @@ Error: %@ Картинки профилей No comment provided by engineer. - - Profile name - Имя профиля - No comment provided by engineer. - - - Profile name: - Имя профиля: - No comment provided by engineer. - Profile password Пароль профиля @@ -5044,7 +5930,7 @@ Error: %@ Profile update will be sent to your contacts. Обновлённый профиль будет отправлен Вашим контактам. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5952,11 @@ Error: %@ Запретить реакции на сообщения. No comment provided by engineer. + + Prohibit reporting messages to moderators. + Запретить жаловаться модераторам группы. + No comment provided by engineer. + Prohibit sending SimpleX links. Запретить отправку ссылок SimpleX. @@ -5073,7 +5964,6 @@ Error: %@ Prohibit sending direct messages to members. - Запретить посылать прямые сообщения членам группы. No comment provided by engineer. @@ -5105,7 +5995,7 @@ Error: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. -Включите в настройках *Сеть и серверы*. +Включите в настройках *Сети и серверов*. No comment provided by engineer. @@ -5133,6 +6023,11 @@ Enable in *Network & servers* settings. Проксированные серверы No comment provided by engineer. + + Proxy requires password + Прокси требует пароль + No comment provided by engineer. + Push notifications Доставка уведомлений @@ -5173,26 +6068,21 @@ Enable in *Network & servers* settings. Узнать больше No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Узнайте больше из нашего GitHub репозитория. - 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). @@ -5323,11 +6213,26 @@ Enable in *Network & servers* settings. Уменьшенное потребление батареи No comment provided by engineer. + + Register + Зарегистрировать + No comment provided by engineer. + + + Register notification token? + Зарегистрировать токен уведомлений? + token info + + + Registered + Зарегистрирован + token status text + Reject Отклонить reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6259,11 @@ Enable in *Network & servers* settings. Удалить No comment provided by engineer. + + Remove archive? + Удалить архив? + No comment provided by engineer. + Remove image Удалить изображение @@ -5361,12 +6271,10 @@ Enable in *Network & servers* settings. Remove member - Удалить члена группы No comment provided by engineer. Remove member? - Удалить члена группы? No comment provided by engineer. @@ -5419,6 +6327,56 @@ Enable in *Network & servers* settings. Ответить chat item action + + Report + Пожаловаться + chat item action + + + Report content: only group moderators will see it. + Пожаловаться на сообщение: увидят только модераторы группы. + report reason + + + Report member profile: only group moderators will see it. + Пожаловаться на профиль: увидят только модераторы группы. + report reason + + + Report other: only group moderators will see it. + Пожаловаться: увидят только модераторы группы. + report reason + + + Report reason? + Причина сообщения? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + Пожаловаться на спам: увидят только модераторы группы. + report reason + + + Report violation: only group moderators will see it. + Пожаловаться на нарушение: увидят только модераторы группы. + report reason + + + Report: %@ + Сообщение о нарушении: %@ + report in notification + + + Reporting messages to moderators is prohibited. + Сообщения о нарушениях запрещены в этой группе. + No comment provided by engineer. + + + Reports + Сообщения о нарушениях + No comment provided by engineer. + Required Обязательно @@ -5504,6 +6462,11 @@ Enable in *Network & servers* settings. Показать chat item action + + Review conditions + Посмотреть условия + No comment provided by engineer. + Revoke Отозвать @@ -5534,6 +6497,11 @@ Enable in *Network & servers* settings. SMP сервер No comment provided by engineer. + + SOCKS proxy + SOCKS прокси + No comment provided by engineer. + Safely receive files Получайте файлы безопасно @@ -5547,21 +6515,21 @@ Enable in *Network & servers* settings. Save Сохранить - chat item action + alert button +chat item action Save (and notify contacts) Сохранить (и уведомить контакты) - No comment provided by engineer. + alert button Save and notify contact Сохранить и уведомить контакт - No comment provided by engineer. + alert button Save and notify group members - Сохранить и уведомить членов группы No comment provided by engineer. @@ -5574,21 +6542,16 @@ Enable in *Network & servers* settings. Сохранить сообщение и обновить группу No comment provided by engineer. - - Save archive - Сохранить архив - No comment provided by engineer. - - - Save auto-accept settings - Сохранить настройки автоприема - No comment provided by engineer. - Save group profile Сохранить профиль группы No comment provided by engineer. + + Save list + Сохранить список + No comment provided by engineer. + Save passphrase and open chat Сохранить пароль и открыть чат @@ -5602,7 +6565,7 @@ Enable in *Network & servers* settings. Save preferences? Сохранить предпочтения? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6580,18 @@ Enable in *Network & servers* settings. Save servers? Сохранить серверы? - No comment provided by engineer. - - - Save settings? - Сохранить настройки? - No comment provided by engineer. + alert title Save welcome message? Сохранить приветственное сообщение? No comment provided by engineer. + + Save your profile? + Сохранить ваш профиль? + alert title + Saved Сохранено @@ -5649,6 +6612,11 @@ Enable in *Network & servers* settings. Сохраненное сообщение message info title + + Saving %lld messages + Сохранение %lld сообщений + No comment provided by engineer. + Scale Масштаб @@ -5729,6 +6697,11 @@ Enable in *Network & servers* settings. Выбрать chat item action + + Select chat profile + Выберите профиль чата + No comment provided by engineer. + Selected %lld Выбрано %lld @@ -5819,9 +6792,9 @@ Enable in *Network & servers* settings. Отправлять уведомления No comment provided by engineer. - - Send notifications: - Отправлять уведомления: + + Send private reports + Вы можете сообщить о нарушениях No comment provided by engineer. @@ -5841,13 +6814,12 @@ Enable in *Network & servers* settings. Send up to 100 last messages to new members. - Отправить до 100 последних сообщений новым членам. No comment provided by engineer. Sender cancelled file transfer. Отправитель отменил передачу файла. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6916,16 @@ Enable in *Network & servers* settings. Отправлено через прокси No comment provided by engineer. + + Server + Сервер + No comment provided by engineer. + + + Server added to operator %@. + Сервер добавлен к оператору %@. + alert message + Server address Адрес сервера @@ -5959,6 +6941,21 @@ Enable in *Network & servers* settings. Адрес сервера несовместим с сетевыми настройками: %@. No comment provided by engineer. + + Server operator changed. + Оператор серверов изменен. + alert title + + + Server operators + Операторы серверов + No comment provided by engineer. + + + Server protocol changed. + Протокол сервера изменен. + alert title + Server requires authorization to create queues, check password Сервер требует авторизации для создания очередей, проверьте пароль @@ -6014,6 +7011,11 @@ Enable in *Network & servers* settings. Установить 1 день No comment provided by engineer. + + Set chat name… + Имя чата… + No comment provided by engineer. + Set contact name… Имя контакта… @@ -6034,6 +7036,11 @@ Enable in *Network & servers* settings. Установите код вместо системной аутентификации. No comment provided by engineer. + + Set message expiration in chats. + Установите срок хранения сообщений в чатах. + No comment provided by engineer. + Set passcode Установить код доступа @@ -6051,7 +7058,6 @@ Enable in *Network & servers* settings. Set the message shown to new members! - Установить сообщение для новых членов группы! No comment provided by engineer. @@ -6064,6 +7070,11 @@ Enable in *Network & servers* settings. Настройки No comment provided by engineer. + + Settings were changed. + Настройки были изменены. + alert message + Shape profile images Форма картинок профилей @@ -6072,22 +7083,38 @@ Enable in *Network & servers* settings. Share Поделиться - chat item action + alert action +chat item action Share 1-time link Поделиться одноразовой ссылкой No comment provided by engineer. + + Share 1-time link with a friend + Поделитесь одноразовой ссылкой с другом + No comment provided by engineer. + + + Share SimpleX address on social media. + Поделитесь SimpleX адресом в социальных сетях. + No comment provided by engineer. + Share address Поделиться адресом No comment provided by engineer. + + Share address publicly + Поделитесь адресом + No comment provided by engineer. + Share address with contacts? Поделиться адресом с контактами? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7126,11 @@ Enable in *Network & servers* settings. Поделиться ссылкой No comment provided by engineer. + + Share profile + Поделиться профилем + No comment provided by engineer. + Share this 1-time invite link Поделиться одноразовой ссылкой-приглашением @@ -6114,6 +7146,10 @@ Enable in *Network & servers* settings. Поделиться с контактами No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Показать QR код @@ -6169,6 +7205,11 @@ Enable in *Network & servers* settings. Адрес SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Безопасность SimpleX Chat была проверена Trail of Bits. @@ -6199,6 +7240,20 @@ Enable in *Network & servers* settings. Адрес SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + Адрес SimpleX и одноразовые ссылки безопасно отправлять через любой мессенджер. + No comment provided by engineer. + + + SimpleX address or 1-time link? + Адрес SimpleX или одноразовая ссылка? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX ссылка-контакт @@ -6219,8 +7274,8 @@ Enable in *Network & servers* settings. SimpleX ссылки chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Ссылки SimpleX запрещены в этой группе. No comment provided by engineer. @@ -6234,6 +7289,11 @@ Enable in *Network & servers* settings. SimpleX одноразовая ссылка simplex link type + + SimpleX protocols reviewed by Trail of Bits. + Аудит SimpleX протоколов от Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Упрощенный режим Инкогнито @@ -6264,6 +7324,11 @@ Enable in *Network & servers* settings. Слабое blur media + + Some app settings were not migrated. + Некоторые настройки приложения не были перенесены. + No comment provided by engineer. + Some file(s) were not exported: Некоторые файл(ы) не были экспортированы: @@ -6279,11 +7344,24 @@ Enable in *Network & servers* settings. Во время импорта произошли некоторые ошибки: No comment provided by engineer. + + Some servers failed the test: +%@ + Серверы не прошли тест: +%@ + alert message + Somebody Контакт notification title + + Spam + Спам + blocking reason +report reason + Square, circle, or anything in between. Квадрат, круг и все, что между ними. @@ -6329,11 +7407,6 @@ Enable in *Network & servers* settings. Остановить чат No comment provided by engineer. - - Stop chat to enable database actions - Остановите чат, чтобы разблокировать операции с архивом чата - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Остановите чат, чтобы экспортировать или импортировать архив чата или удалить данные чата. Вы не сможете получать и отправлять сообщения, пока чат остановлен. @@ -6362,18 +7435,23 @@ Enable in *Network & servers* settings. Stop sharing Прекратить делиться - No comment provided by engineer. + alert action Stop sharing address? Прекратить делиться адресом? - No comment provided by engineer. + alert title Stopping chat Остановка чата No comment provided by engineer. + + Storage + Хранилище + No comment provided by engineer. + Strong Сильное @@ -6404,6 +7482,16 @@ Enable in *Network & servers* settings. Поддержать SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Переключайте звук и видео во время звонка. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Переключайте профиль чата для одноразовых приглашений. + No comment provided by engineer. + System Системная @@ -6424,6 +7512,11 @@ Enable in *Network & servers* settings. Таймаут TCP соединения No comment provided by engineer. + + TCP port for messaging + TCP-порт для отправки сообщений + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7532,21 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Хвост + No comment provided by engineer. + Take picture Сделать фото No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. + No comment provided by engineer. + Tap button Нажмите кнопку @@ -6482,13 +7585,18 @@ Enable in *Network & servers* settings. Temporary file error Временная ошибка файла - No comment provided by engineer. + file error alert title Test failed at step %@. Ошибка теста на шаге %@. server test failure + + Test notifications + Протестировать уведомления + No comment provided by engineer. + Test server Тестировать сервер @@ -6502,7 +7610,7 @@ Enable in *Network & servers* settings. Tests failed! Ошибка тестов! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7627,6 @@ Enable in *Network & servers* settings. Благодаря пользователям – добавьте переводы через Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Первая в мире платформа без идентификаторов пользователей. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7639,11 @@ It can happen because of some bug or when the connection is compromised.Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + Приложение улучшает конфиденциальность используя разных операторов в каждом разговоре. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов). @@ -6551,6 +7659,11 @@ It can happen because of some bug or when the connection is compromised.Этот QR код не является SimpleX-ccылкой. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. + No comment provided by engineer. + The connection you accepted will be cancelled! Подтвержденное соединение будет отменено! @@ -6571,6 +7684,11 @@ It can happen because of some bug or when the connection is compromised.Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения! No comment provided by engineer. + + The future of messaging + Будущее коммуникаций + No comment provided by engineer. + The hash of the previous message is different. Хэш предыдущего сообщения отличается. @@ -6578,27 +7696,18 @@ It can happen because of some bug or when the connection is compromised. The message will be deleted for all members. - Сообщение будет удалено для всех членов группы. No comment provided by engineer. 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. - Сообщения будут помечены как удаленные для всех членов группы. - No comment provided by engineer. - - - The next generation of private messaging - Новое поколение приватных сообщений No comment provided by engineer. @@ -6606,9 +7715,14 @@ It can happen because of some bug or when the connection is compromised.Предыдущая версия данных чата не удалена при перемещении, её можно удалить. No comment provided by engineer. - - The profile is only shared with your contacts. - Профиль отправляется только Вашим контактам. + + The same conditions will apply to operator **%@**. + Те же самые условия будут приняты для оператора **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + Второй оператор серверов в приложении! No comment provided by engineer. @@ -6626,16 +7740,31 @@ It can happen because of some bug or when the connection is compromised.Серверы для новых соединений Вашего текущего профиля чата **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + Серверы для новых файлов Вашего текущего профиля **%@**. + No comment provided by engineer. + 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. + Загруженный архив базы данных будет навсегда удален с серверов. + No comment provided by engineer. + Themes Темы No comment provided by engineer. + + These conditions will also apply for: **%@**. + Эти условия также будут применены к: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Установки для Вашего активного профиля **%@**. @@ -6656,6 +7785,11 @@ It can happen because of some bug or when the connection is compromised.Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Это действие нельзя отменить - сообщения в этом чате, отправленные или полученные раньше чем выбрано, будут удалены. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. @@ -6683,7 +7817,6 @@ It can happen because of some bug or when the connection is compromised. This group has over %lld members, delivery receipts are not sent. - В группе более %lld членов, отчёты о доставке выключены. No comment provided by engineer. @@ -6701,11 +7834,20 @@ It can happen because of some bug or when the connection is compromised.Это ваша собственная одноразовая ссылка! 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. + No comment provided by engineer. + 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. + Это сообщение было удалено или еще не получено. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Эта настройка применяется к сообщениям в Вашем текущем профиле чата **%@**. @@ -6736,9 +7878,9 @@ It can happen because of some bug or when the connection is compromised.Чтобы соединиться No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Чтобы защитить Вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта. + + To protect against your link being replaced, you can compare contact security codes. + Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности. No comment provided by engineer. @@ -6758,6 +7900,26 @@ You will be prompted to complete authentication before this feature is enabled.< Вам будет нужно пройти аутентификацию для включения блокировки. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта. + No comment provided by engineer. + + + To receive + Для получения + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Для записи речи, пожалуйста, дайте разрешение на использование микрофона. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Для записи видео, пожалуйста, дайте разрешение на использование камеры. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Для записи голосового сообщения, пожалуйста разрешите доступ к микрофону. @@ -6768,11 +7930,21 @@ You will be prompted to complete authentication before this feature is enabled.< Чтобы показать Ваш скрытый профиль, введите его пароль в поле поиска на странице **Ваши профили чата**. No comment provided by engineer. + + To send + Для оправки + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Для поддержки мгновенный доставки уведомлений данные чата должны быть перемещены. No comment provided by engineer. + + 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 шифрование с Вашим контактом сравните (или сканируйте) код безопасности на Ваших устройствах. @@ -6788,6 +7960,11 @@ You will be prompted to complete authentication before this feature is enabled.< Установите режим Инкогнито при соединении. No comment provided by engineer. + + Token status: %@. + Статус токена: %@. + token status + Toolbar opacity Прозрачность тулбара @@ -6850,17 +8027,19 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member - Разблокировать члена группы No comment provided by engineer. Unblock member for all? - Разблокировать члена для всех? No comment provided by engineer. Unblock member? - Разблокировать члена группы? + No comment provided by engineer. + + + Undelivered messages + Недоставленные сообщения No comment provided by engineer. @@ -6911,7 +8090,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Неизвестные серверы! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,16 +8127,19 @@ To connect, please ask your contact to create another connection link and check Unmute Уведомлять - swipe action + notification label action Unread Не прочитано swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. - До 100 последних сообщений отправляются новым членам. No comment provided by engineer. @@ -6980,6 +8162,11 @@ To connect, please ask your contact to create another connection link and check Обновить настройки? No comment provided by engineer. + + Updated conditions + Обновленные условия + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Обновление настроек приведет к сбросу и установке нового соединения со всеми серверами. @@ -7020,16 +8207,35 @@ To connect, please ask your contact to create another connection link and check Загрузка архива No comment provided by engineer. + + Use %@ + Использовать %@ + No comment provided by engineer. + Use .onion hosts Использовать .onion хосты No comment provided by engineer. + + Use SOCKS proxy + Использовать SOCKS прокси + No comment provided by engineer. + Use SimpleX Chat servers? Использовать серверы предосталенные SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + Использовать TCP-порт %@, когда порт не указан. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Использовать чат @@ -7040,6 +8246,16 @@ To connect, please ask your contact to create another connection link and check Использовать активный профиль No comment provided by engineer. + + Use for files + Использовать для файлов + No comment provided by engineer. + + + Use for messages + Использовать для сообщений + No comment provided by engineer. + Use for new connections Использовать для новых соединений @@ -7080,6 +8296,15 @@ To connect, please ask your contact to create another connection link and check Использовать сервер No comment provided by engineer. + + Use servers + Использовать серверы + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Используйте приложение во время звонка. @@ -7090,9 +8315,9 @@ To connect, please ask your contact to create another connection link and check Используйте приложение одной рукой. No comment provided by engineer. - - User profile - Профиль чата + + Use web port + Использовать веб-порт No comment provided by engineer. @@ -7100,6 +8325,11 @@ To connect, please ask your contact to create another connection link and check Выбор пользователя No comment provided by engineer. + + Username + Имя пользователя + No comment provided by engineer. + Using SimpleX Chat servers. Используются серверы, предоставленные SimpleX Chat. @@ -7170,11 +8400,21 @@ To connect, please ask your contact to create another connection link and check Видео и файлы до 1гб No comment provided by engineer. + + View conditions + Посмотреть условия + No comment provided by engineer. + View security code Показать код безопасности No comment provided by engineer. + + View updated conditions + Посмотреть измененные условия + No comment provided by engineer. + Visible history Доступ к истории @@ -7190,8 +8430,8 @@ To connect, please ask your contact to create another connection link and check Голосовые сообщения запрещены в этом чате. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Голосовые сообщения запрещены в этой группе. No comment provided by engineer. @@ -7285,9 +8525,9 @@ To connect, please ask your contact to create another connection link and check Во время соединения аудио и видео звонков. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Когда Вы получите запрос на соединение, Вы можете принять или отклонить его. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем. No comment provided by engineer. @@ -7333,7 +8573,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8600,6 @@ To connect, please ask your contact to create another connection link and check XFTP сервер No comment provided by engineer. - - You - Вы - No comment provided by engineer. - You **must not** use the same database on two devices. Вы **не должны** использовать одну и ту же базу данных на двух устройствах. @@ -7390,6 +8625,11 @@ To connect, please ask your contact to create another connection link and check Вы уже соединены с контактом %@. No comment provided by engineer. + + You are already connected with %@. + Вы уже соединены с %@. + No comment provided by engineer. + You are already connecting to %@. Вы уже соединяетесь с %@. @@ -7452,6 +8692,11 @@ Repeat join request? Вы можете изменить это в настройках Интерфейса. No comment provided by engineer. + + You can configure servers via settings. + Вы можете настроить серверы позже. + No comment provided by engineer. + You can create it later Вы можете создать его позже @@ -7492,6 +8737,11 @@ Repeat join request? Вы можете отправлять сообщения %@ из Архивированных контактов. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + Вы можете установить имя соединения, чтобы запомнить кому Вы отправили ссылку. + No comment provided by engineer. + You can set lock screen notification preview via settings. Вы можете установить просмотр уведомлений на экране блокировки в настройках. @@ -7499,7 +8749,6 @@ 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. - Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. No comment provided by engineer. @@ -7507,11 +8756,6 @@ Repeat join request? Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Вы можете запустить чат через Настройки приложения или перезапустив приложение. @@ -7535,23 +8779,23 @@ Repeat join request? You can view invitation link again in connection details. Вы можете увидеть ссылку-приглашение снова открыв соединение. - No comment provided by engineer. + alert message You can't send messages! Вы не можете отправлять сообщения! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Вы определяете через какие серверы Вы **получаете сообщения**, Ваши контакты - серверы, которые Вы используете для отправки. - 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 already requested connection via this address! Вы уже запросили соединение через этот адрес! @@ -7581,7 +8825,6 @@ Repeat connection request? You joined this group. Connecting to inviting group member. - Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы. No comment provided by engineer. @@ -7619,6 +8862,11 @@ Repeat connection request? Вы отправили приглашение в группу No comment provided by engineer. + + You should receive notifications. + Вы должны получать уведомления. + token info + You will be connected to group when the group host's device is online, please wait or check later! Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже! @@ -7646,7 +8894,6 @@ Repeat connection request? You will connect to all group members. - Вы соединитесь со всеми членами группы. No comment provided by engineer. @@ -7654,6 +8901,11 @@ Repeat connection request? Вы все равно получите звонки и уведомления в профилях без звука, когда они активные. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + Вы прекратите получать сообщения в этом разговоре. История будет сохранена. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Вы перестанете получать сообщения от этой группы. История чата будет сохранена. @@ -7674,31 +8926,16 @@ Repeat connection request? Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие Вашего основного профиля, приглашать контакты не разрешено No comment provided by engineer. - - Your %@ servers - Ваши %@ серверы - No comment provided by engineer. - Your ICE servers Ваши ICE серверы No comment provided by engineer. - - Your SMP servers - Ваши SMP серверы - No comment provided by engineer. - Your SimpleX address Ваш адрес SimpleX No comment provided by engineer. - - Your XFTP servers - Ваши XFTP серверы - No comment provided by engineer. - Your calls Ваши звонки @@ -7714,11 +8951,21 @@ Repeat connection request? База данных НЕ зашифрована. Установите пароль, чтобы защитить Ваши данные. No comment provided by engineer. + + Your chat preferences + Ваши настройки чата + alert title + Your chat profiles Ваши профили чата No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Соединение было перемещено на %@, но при смене профиля произошла неожиданная ошибка. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ваш контакт отправил файл, размер которого превышает максимальный размер (%@). @@ -7734,6 +8981,11 @@ Repeat connection request? Ваши контакты сохранятся. 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. Текущие данные Вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными. @@ -7764,33 +9016,36 @@ Repeat connection request? Будет отправлен Ваш профиль **%@**. 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 only shared with your contacts. + Ваш профиль храниться на Вашем устройстве и отправляется только контактам. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. 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 random profile Случайный профиль No comment provided by engineer. - - Your server - Ваш сервер - No comment provided by engineer. - Your server address Адрес Вашего сервера No comment provided by engineer. + + Your servers + Ваши серверы + No comment provided by engineer. + Your settings Настройки @@ -7831,6 +9086,11 @@ SimpleX серверы не могут получить доступ к Ваше принятый звонок call status + + accepted invitation + принятое приглашение + chat list item title + admin админ @@ -7853,7 +9113,6 @@ SimpleX серверы не могут получить доступ к Ваше all members - все члены feature role @@ -7866,6 +9125,11 @@ SimpleX серверы не могут получить доступ к Ваше и %lld других событий No comment provided by engineer. + + archived report + заархивированное сообщение о нарушении + No comment provided by engineer. + attempts попытки @@ -7904,7 +9168,8 @@ SimpleX серверы не могут получить доступ к Ваше blocked by admin заблокировано администратором - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9284,7 @@ SimpleX серверы не могут получить доступ к Ваше connecting… соединяется… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9339,8 @@ SimpleX серверы не могут получить доступ к Ваше default (%@) по умолчанию (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9467,6 @@ SimpleX серверы не могут получить доступ к Ваше ошибка No comment provided by engineer. - - event happened - событие произошло - No comment provided by engineer. - expired истекло @@ -8333,12 +9594,10 @@ SimpleX серверы не могут получить доступ к Ваше member - член группы member role member %1$@ changed to %2$@ - член %1$@ изменился на %2$@ profile update event chat item @@ -8376,20 +9635,20 @@ SimpleX серверы не могут получить доступ к Ваше удалено %@ marked deleted chat item preview text + + moderator + модератор + member role + months месяцев time unit - - mute - без звука - No comment provided by engineer. - never никогда - No comment provided by engineer. + delete after time new message @@ -8420,8 +9679,8 @@ SimpleX серверы не могут получить доступ к Ваше off нет enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9722,16 @@ SimpleX серверы не могут получить доступ к Ваше peer-to-peer No comment provided by engineer. + + pending + ожидает + No comment provided by engineer. + + + pending approval + ожидает утверждения + No comment provided by engineer. + quantum resistant e2e encryption квантово-устойчивое e2e шифрование @@ -8478,6 +9747,11 @@ SimpleX серверы не могут получить доступ к Ваше получено подтверждение… No comment provided by engineer. + + rejected + отклонён + No comment provided by engineer. + rejected call отклонённый звонок @@ -8508,6 +9782,11 @@ SimpleX серверы не могут получить доступ к Ваше удалил(а) Вас из группы rcv group event chat item + + requested to connect + запрошено соединение + chat list item title + saved сохранено @@ -8607,11 +9886,6 @@ last received msg: %2$@ неизвестный статус No comment provided by engineer. - - unmute - уведомлять - No comment provided by engineer. - unprotected незащищённый @@ -8776,7 +10050,7 @@ last received msg: %2$@
- +
@@ -8813,7 +10087,7 @@ last received msg: %2$@
- +
@@ -8833,9 +10107,40 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + %d новых сообщений + notification body + + + From %d chat(s) + notification body + + + From: %@ + От: %@ + notification body + + + New events + Новые события + notification + + + New messages + Новые сообщения + notification + + +
- +
@@ -8857,7 +10162,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/contents.json b/apps/ios/SimpleX Localizations/ru.xcloc/contents.json index a28b0ed489..b49b25d653 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/ru.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "ru", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 646a94a337..671dd87d7d 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (สามารถคัดลอกได้) @@ -120,6 +93,14 @@ %@ ได้รับการตรวจสอบแล้ว No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded No comment provided by engineer. @@ -129,6 +110,10 @@ %@ อยากเชื่อมต่อ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -147,11 +132,31 @@ %d วัน time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ชั่วโมง time interval + + %d messages not forwarded + alert title + %d min %d นาที @@ -167,6 +172,10 @@ %d วินาที time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d ข้อความที่ถูกข้าม @@ -231,11 +240,6 @@ %lld new interface languages No comment provided by engineer. - - %lld second(s) - %lld วินาที - No comment provided by engineer. - %lld seconds %lld วินาที @@ -286,11 +290,6 @@ %u ข้อความที่ถูกข้าม No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) No comment provided by engineer. @@ -299,31 +298,21 @@ (this device v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **เพิ่มผู้ติดต่อใหม่**: เพื่อสร้างคิวอาร์โค้ดแบบใช้ครั้งเดียวหรือลิงก์สำหรับผู้ติดต่อของคุณ + + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **เป็นส่วนตัวมากขึ้น**: ตรวจสอบข้อความใหม่ทุกๆ 20 นาที โทเค็นอุปกรณ์แชร์กับเซิร์ฟเวอร์ SimpleX Chat แต่ไม่ระบุจำนวนผู้ติดต่อหรือข้อความที่คุณมี No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป) No comment provided by engineer. @@ -336,11 +325,15 @@ **โปรดทราบ**: คุณจะไม่สามารถกู้คืนหรือเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **แนะนำ**: โทเค็นอุปกรณ์และการแจ้งเตือนจะถูกส่งไปยังเซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat แต่ไม่ใช่เนื้อหาข้อความ ขนาด หรือผู้ที่ส่ง No comment provided by engineer. + + **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 @@ -365,11 +358,6 @@ \*ตัวหนา* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -400,11 +388,6 @@ - ประวัติการแก้ไข No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec time to disappear @@ -417,7 +400,8 @@ 1 day 1 วัน - time interval + delete after time +time interval 1 hour @@ -432,12 +416,26 @@ 1 month 1 เดือน - time interval + delete after time +time interval 1 week 1 สัปดาห์ - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. 5 minutes @@ -454,11 +452,6 @@ 30 วินาที No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -507,19 +500,13 @@ ยกเลิกการเปลี่ยนที่อยู่? No comment provided by engineer. - - About SimpleX - เกี่ยวกับ SimpleX - No comment provided by engineer. - About SimpleX Chat เกี่ยวกับ SimpleX Chat No comment provided by engineer. - - About SimpleX address - เกี่ยวกับที่อยู่ SimpleX + + About operators No comment provided by engineer. @@ -530,8 +517,12 @@ Accept รับ accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + No comment provided by engineer. Accept connection request? @@ -546,7 +537,11 @@ Accept incognito ยอมรับโหมดไม่ระบุตัวตน accept contact request via notification - swipe action +swipe action + + + Accepted conditions + No comment provided by engineer. Acknowledged @@ -556,6 +551,10 @@ Acknowledgement errors No comment provided by engineer. + + Active + token status text + Active connections No comment provided by engineer. @@ -565,13 +564,12 @@ เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ No comment provided by engineer. - - Add contact + + Add friends No comment provided by engineer. - - Add preset servers - เพิ่มเซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า + + Add list No comment provided by engineer. @@ -589,16 +587,36 @@ เพิ่มเซิร์ฟเวอร์โดยการสแกนรหัสคิวอาร์โค้ด No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device เพิ่มเข้าไปในอุปกรณ์อื่น No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message เพิ่มข้อความต้อนรับ No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -621,6 +639,14 @@ การเปลี่ยนแปลงที่อยู่จะถูกยกเลิก จะใช้ที่อยู่เก่าของผู้รับ No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -639,6 +665,10 @@ Advanced settings No comment provided by engineer. + + All + No comment provided by engineer. + All app data is deleted. ข้อมูลแอปทั้งหมดถูกลบแล้ว. @@ -649,13 +679,17 @@ แชทและข้อความทั้งหมดจะถูกลบ - การดำเนินการนี้ไม่สามารถยกเลิกได้! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. ข้อมูลทั้งหมดจะถูกลบเมื่อถูกป้อน No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. @@ -663,6 +697,10 @@ สมาชิกในกลุ่มทุกคนจะยังคงเชื่อมต่ออยู่. 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! No comment provided by engineer. @@ -678,6 +716,14 @@ All profiles + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -751,6 +797,10 @@ อนุญาตให้ลบข้อความที่ส่งไปแล้วอย่างถาวร No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. No comment provided by engineer. @@ -827,11 +877,20 @@ โปรไฟล์แชทที่ว่างเปล่าพร้อมชื่อที่ให้ไว้ได้ถูกสร้างขึ้นและแอปจะเปิดตามปกติ No comment provided by engineer. + + Another reason + report reason + Answer call รับสาย No comment provided by engineer. + + Anybody can host servers. + โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ + No comment provided by engineer. + App build: %@ รุ่นแอป: %@ @@ -845,6 +904,10 @@ App encrypts new local files (except videos). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon ไอคอนแอป @@ -860,6 +923,10 @@ รหัสผ่านแอปจะถูกแทนที่ด้วยรหัสผ่านที่ทำลายตัวเอง No comment provided by engineer. + + App session + No comment provided by engineer. + App version เวอร์ชันแอป @@ -883,6 +950,18 @@ Apply to No comment provided by engineer. + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? + No comment provided by engineer. + Archive and upload No comment provided by engineer. @@ -891,6 +970,18 @@ Archive contacts to chat later. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts No comment provided by engineer. @@ -959,6 +1050,10 @@ ยอมรับภาพอัตโนมัติ No comment provided by engineer. + + Auto-accept settings + alert title + Back กลับ @@ -982,10 +1077,22 @@ แฮชข้อความไม่ดี No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + No comment provided by engineer. + Better messages ข้อความที่ดีขึ้น @@ -995,6 +1102,22 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1064,11 +1187,29 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + No comment provided by engineer. + + + Business chats + No comment provided by engineer. + + + Businesses + 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: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! สิ้นสุดการโทรแล้ว! @@ -1112,7 +1253,8 @@ Cancel ยกเลิก - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1130,7 +1272,7 @@ Cannot receive file ไม่สามารถรับไฟล์ได้ - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1145,6 +1287,14 @@ เปลี่ยน No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + authentication reason + Change database passphrase? เปลี่ยนรหัสผ่านฐานข้อมูล? @@ -1189,11 +1339,18 @@ Change self-destruct passcode เปลี่ยนรหัสผ่านแบบทำลายตัวเอง authentication reason - set passcode view +set passcode view - - Chat archive - ที่เก็บแชทถาวร + + Chat + No comment provided by engineer. + + + Chat already exists + No comment provided by engineer. + + + Chat already exists! No comment provided by engineer. @@ -1251,19 +1408,44 @@ ค่ากําหนดในการแชท No comment provided by engineer. + + Chat preferences were changed. + alert message + + + Chat profile + โปรไฟล์ผู้ใช้ + No comment provided by engineer. + Chat theme No comment provided by engineer. + + 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. + Chats แชท No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. ตรวจสอบที่อยู่เซิร์ฟเวอร์แล้วลองอีกครั้ง - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1311,6 +1493,14 @@ ลบการสนทนา? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? No comment provided by engineer. @@ -1328,6 +1518,10 @@ Color mode No comment provided by engineer. + + Community guidelines violation + report reason + Compare file เปรียบเทียบไฟล์ @@ -1342,13 +1536,41 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers กำหนดค่าเซิร์ฟเวอร์ ICE No comment provided by engineer. - - Configured %@ servers + + Configure server operators No comment provided by engineer. @@ -1396,6 +1618,10 @@ Confirm upload No comment provided by engineer. + + Confirmed + token status text + Connect เชื่อมต่อ @@ -1495,6 +1721,10 @@ This is your own one-time link! Connection and servers status. No comment provided by engineer. + + Connection blocked + No comment provided by engineer. + Connection error การเชื่อมต่อผิดพลาด @@ -1505,6 +1735,15 @@ This is your own one-time link! การเชื่อมต่อผิดพลาด (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications No comment provided by engineer. @@ -1514,6 +1753,14 @@ This is your own one-time link! ส่งคําขอเชื่อมต่อแล้ว! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1583,6 +1830,10 @@ This is your own one-time link! ผู้ติดต่อสามารถทําเครื่องหมายข้อความเพื่อลบได้ คุณจะสามารถดูได้ No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue ดำเนินการต่อ @@ -1606,6 +1857,10 @@ This is your own one-time link! รุ่นหลัก: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -1615,6 +1870,10 @@ This is your own one-time link! สร้าง No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address สร้างที่อยู่ SimpleX @@ -1624,11 +1883,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - สร้างที่อยู่เพื่อให้ผู้อื่นเชื่อมต่อกับคุณ - No comment provided by engineer. - Create file สร้างไฟล์ @@ -1648,6 +1902,10 @@ This is your own one-time link! สร้างลิงค์ No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 No comment provided by engineer. @@ -1683,11 +1941,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - สร้างเมื่อ %@ - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -1701,6 +1954,10 @@ This is your own one-time link! รหัสผ่านปัจจุบัน No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… รหัสผ่านปัจจุบัน… @@ -1720,6 +1977,10 @@ This is your own one-time link! เวลาที่กําหนดเอง No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -1848,8 +2109,8 @@ This is your own one-time link! Delete ลบ - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -1883,14 +2144,12 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - ลบที่เก็บถาวร + + Delete chat No comment provided by engineer. - - Delete chat archive? - ลบที่เก็บแชทถาวร? + + Delete chat messages from your device. No comment provided by engineer. @@ -1903,6 +2162,10 @@ This is your own one-time link! ลบโปรไฟล์แชทไหม? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection ลบการเชื่อมต่อ @@ -1976,6 +2239,10 @@ This is your own one-time link! ลบลิงค์ ไหม? No comment provided by engineer. + + Delete list? + alert title + Delete member message? ลบข้อความสมาชิก? @@ -1989,7 +2256,7 @@ This is your own one-time link! Delete messages ลบข้อความ - No comment provided by engineer. + alert button Delete messages after @@ -2006,6 +2273,10 @@ This is your own one-time link! ลบฐานข้อมูลเก่า? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? ลบการเชื่อมต่อที่รอดำเนินการหรือไม่? @@ -2021,6 +2292,10 @@ This is your own one-time link! ลบคิว server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. No comment provided by engineer. @@ -2052,6 +2327,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery No comment provided by engineer. @@ -2142,8 +2421,12 @@ This is your own one-time link! ข้อความโดยตรง chat feature - - Direct messages between members are prohibited in this group. + + 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. @@ -2157,6 +2440,14 @@ This is your own one-time link! ปิดการใช้งาน SimpleX Lock authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all ปิดการใช้งานสำหรับทุกคน @@ -2181,8 +2472,8 @@ This is your own one-time link! ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในแชทนี้ No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -2235,6 +2526,14 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address อย่าสร้างที่อยู่ @@ -2245,11 +2544,19 @@ This is your own one-time link! อย่าเปิดใช้งาน No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again ไม่ต้องแสดงอีก No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat ปรับลดรุ่นและเปิดแชท @@ -2257,7 +2564,8 @@ This is your own one-time link! Download - chat item action + alert button +chat item action Download errors @@ -2272,6 +2580,10 @@ This is your own one-time link! ดาวน์โหลดไฟล์ server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2298,6 +2610,10 @@ This is your own one-time link! ระยะเวลา No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit แก้ไข @@ -2318,6 +2634,10 @@ This is your own one-time link! เปิดใช้งาน (เก็บการแทนที่) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock เปิดใช้งาน SimpleX Lock @@ -2331,7 +2651,7 @@ This is your own one-time link! Enable automatic message deletion? เปิดใช้งานการลบข้อความอัตโนมัติ? - No comment provided by engineer. + alert title Enable camera access @@ -2449,6 +2769,10 @@ This is your own one-time link! Encryption re-negotiation failed. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode ใส่รหัสผ่าน @@ -2510,26 +2834,33 @@ This is your own one-time link! เกิดข้อผิดพลาดในการยกเลิกการเปลี่ยนที่อยู่ No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request เกิดข้อผิดพลาดในการรับคำขอติดต่อ No comment provided by engineer. - - Error accessing database file - เกิดข้อผิดพลาดในการเข้าถึงไฟล์ฐานข้อมูล - No comment provided by engineer. - Error adding member(s) เกิดข้อผิดพลาดในการเพิ่มสมาชิก No comment provided by engineer. + + Error adding server + alert title + Error changing address เกิดข้อผิดพลาดในการเปลี่ยนที่อยู่ No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role เกิดข้อผิดพลาดในการเปลี่ยนบทบาท @@ -2540,6 +2871,14 @@ This is your own one-time link! เกิดข้อผิดพลาดในการเปลี่ยนการตั้งค่า No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2559,6 +2898,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการสร้างลิงก์กลุ่ม No comment provided by engineer. + + Error creating list + alert title + Error creating member contact No comment provided by engineer. @@ -2572,6 +2915,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการสร้างโปรไฟล์! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file No comment provided by engineer. @@ -2649,9 +2996,12 @@ This is your own one-time link! เกิดข้อผิดพลาดในการเข้าร่วมกลุ่ม No comment provided by engineer. - - Error loading %@ servers - โหลดเซิร์ฟเวอร์ %@ ผิดพลาด + + Error loading servers + alert title + + + Error migrating settings No comment provided by engineer. @@ -2661,7 +3011,7 @@ This is your own one-time link! Error receiving file เกิดข้อผิดพลาดในการรับไฟล์ - No comment provided by engineer. + alert title Error reconnecting server @@ -2671,25 +3021,32 @@ This is your own one-time link! Error reconnecting servers No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member เกิดข้อผิดพลาดในการลบสมาชิก No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - เกิดข้อผิดพลาดในการบันทึกเซิร์ฟเวอร์ %@ - No comment provided by engineer. - Error saving ICE servers เกิดข้อผิดพลาดในการบันทึกเซิร์ฟเวอร์ ICE No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile เกิดข้อผิดพลาดในการบันทึกโปรไฟล์กลุ่ม @@ -2705,6 +3062,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการบันทึกรหัสผ่านไปยัง keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2747,16 +3108,24 @@ This is your own one-time link! เกิดข้อผิดพลาดในการหยุดแชท No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! เกิดข้อผิดพลาดในการเปลี่ยนโปรไฟล์! - No comment provided by engineer. + alertTitle Error synchronizing connection เกิดข้อผิดพลาดในการซิงโครไนซ์การเชื่อมต่อ No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link เกิดข้อผิดพลาดในการอัปเดตลิงก์กลุ่ม @@ -2767,6 +3136,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการอัปเดตข้อความ No comment provided by engineer. + + Error updating server + alert title + Error updating settings เกิดข้อผิดพลาดในการอัปเดตการตั้งค่า @@ -2793,8 +3166,9 @@ This is your own one-time link! Error: %@ ข้อผิดพลาด: % @ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2810,6 +3184,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. แม้ในขณะที่ปิดใช้งานในการสนทนา @@ -2824,6 +3202,10 @@ This is your own one-time link! Expand chat item action + + Expired + token status text + Export database ส่งออกฐานข้อมูล @@ -2862,18 +3244,40 @@ This is your own one-time link! รวดเร็วและไม่ต้องรอจนกว่าผู้ส่งจะออนไลน์! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite ที่ชอบ swipe action + + Favorites + No comment provided by engineer. + File error - No comment provided by engineer. + file error alert title + + + File errors: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -2925,8 +3329,8 @@ This is your own one-time link! ไฟล์และสื่อ chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. ไฟล์และสื่อเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -2992,19 +3396,59 @@ This is your own one-time link! การแก้ไขไม่สนับสนุนโดยสมาชิกกลุ่ม No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + servers error + For console สำหรับคอนโซล No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3013,6 +3457,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3054,11 +3502,6 @@ Error: %2$@ ชื่อเต็ม (ไม่บังคับ) No comment provided by engineer. - - Full name: - ชื่อเต็ม: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -3078,6 +3521,10 @@ Error: %2$@ GIFs และสติกเกอร์ No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! message preview @@ -3139,40 +3586,6 @@ Error: %2$@ ลิงค์กลุ่ม No comment provided by engineer. - - Group members can add message reactions. - สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร - No comment provided by engineer. - - - Group members can send SimpleX links. - No comment provided by engineer. - - - Group members can send direct messages. - สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้ - No comment provided by engineer. - - - Group members can send disappearing messages. - สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ - No comment provided by engineer. - - - Group members can send files and media. - สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ - No comment provided by engineer. - - - Group members can send voice messages. - สมาชิกกลุ่มสามารถส่งข้อความเสียง - No comment provided by engineer. - Group message: ข้อความกลุ่ม: @@ -3213,11 +3626,19 @@ Error: %2$@ กลุ่มจะถูกลบสำหรับคุณ - ไม่สามารถยกเลิกได้! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help ความช่วยเหลือ No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden ซ่อนอยู่ @@ -3267,10 +3688,17 @@ Error: %2$@ วิธีการ SimpleX ทํางานอย่างไร No comment provided by engineer. + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + No comment provided by engineer. + How it works - มันทำงานอย่างไร - No comment provided by engineer. + alert button How to @@ -3296,6 +3724,10 @@ Error: %2$@ เซิร์ฟเวอร์ ICE (หนึ่งเครื่องต่อสาย) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. หากคุณไม่สามารถพบกันในชีวิตจริงได้ ให้แสดงคิวอาร์โค้ดในวิดีโอคอล หรือแชร์ลิงก์ @@ -3336,8 +3768,8 @@ Error: %2$@ โดยทันที No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam มีภูมิคุ้มกันต่อสแปมและการละเมิด No comment provided by engineer. @@ -3368,6 +3800,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -3395,6 +3832,14 @@ Error: %2$@ In-call sounds No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito ไม่ระบุตัวตน @@ -3462,6 +3907,11 @@ Error: %2$@ ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + ทันที + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3469,11 +3919,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - ทันที - No comment provided by engineer. - Interface อินเตอร์เฟซ @@ -3483,6 +3928,26 @@ Error: %2$@ Interface colors No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code No comment provided by engineer. @@ -3515,7 +3980,7 @@ Error: %2$@ Invalid server address! ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง! - No comment provided by engineer. + alert title Invalid status @@ -3536,6 +4001,10 @@ Error: %2$@ เชิญสมาชิก No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group เชิญเข้าร่วมกลุ่ม @@ -3551,8 +4020,8 @@ Error: %2$@ ไม่สามารถลบข้อความแบบแก้ไขไม่ได้ในแชทนี้ No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. การลบข้อความแบบแก้ไขไม่ได้เป็นสิ่งที่ห้ามในกลุ่มนี้ No comment provided by engineer. @@ -3635,7 +4104,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3647,7 +4116,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3684,6 +4153,14 @@ This is your link for group %@! ออกจาก swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group ออกจากกลุ่ม @@ -3721,6 +4198,18 @@ This is your link for group %@! Linked desktops No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! ข้อความสด! @@ -3731,11 +4220,6 @@ This is your link for group %@! ข้อความสด No comment provided by engineer. - - Local - ในเครื่อง - No comment provided by engineer. - Local name ชื่อภายในเครื่องเท่านั้น @@ -3756,11 +4240,6 @@ This is your link for group %@! โหมดล็อค No comment provided by engineer. - - Make a private connection - สร้างการเชื่อมต่อแบบส่วนตัว - No comment provided by engineer. - Make one message disappear ทำให้ข้อความหายไปหนึ่งข้อความ @@ -3771,21 +4250,11 @@ This is your link for group %@! ทำให้โปรไฟล์เป็นส่วนตัว! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ %@ อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน (%@) - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ WebRTC ICE อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - หลายคนถามว่า: *หาก SimpleX ไม่มีตัวระบุผู้ใช้ จะส่งข้อความได้อย่างไร?* - No comment provided by engineer. - Mark deleted for everyone ทำเครื่องหมายว่าลบแล้วสำหรับทุกคน @@ -3828,6 +4297,14 @@ This is your link for group %@! Member inactive item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. บทบาทของสมาชิกจะถูกเปลี่ยนเป็น "%@" สมาชิกกลุ่มทั้งหมดจะได้รับแจ้ง @@ -3838,11 +4315,57 @@ This is your link for group %@! บทบาทของสมาชิกจะถูกเปลี่ยนเป็น "%@" สมาชิกจะได้รับคำเชิญใหม่ No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้! No comment provided by engineer. + + Members can add message reactions. + สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้ + No comment provided by engineer. + + + Members can send disappearing messages. + สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ + No comment provided by engineer. + + + Members can send files and media. + สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ + No comment provided by engineer. + + + Members can send voice messages. + สมาชิกกลุ่มสามารถส่งข้อความเสียง + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus No comment provided by engineer. @@ -3888,8 +4411,8 @@ This is your link for group %@! ห้ามแสดงปฏิกิริยาบนข้อความในแชทนี้ No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. ปฏิกิริยาบนข้อความเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -3901,6 +4424,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3936,6 +4463,10 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received No comment provided by engineer. @@ -3944,6 +4475,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4000,9 +4535,9 @@ This is your link for group %@! การโยกย้ายเสร็จสมบูรณ์ No comment provided by engineer. - - Migrations: %@ - การย้ายข้อมูล: %@ + + Migrations: + การย้ายข้อมูล No comment provided by engineer. @@ -4020,6 +4555,10 @@ This is your link for group %@! กลั่นกรองที่: %@ copied message info + + More + swipe action + More improvements are coming soon! การปรับปรุงเพิ่มเติมกำลังจะมาเร็ว ๆ นี้! @@ -4029,6 +4568,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. item status description @@ -4041,7 +4584,11 @@ This is your link for group %@! Mute ปิดเสียง - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4062,6 +4609,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4070,6 +4621,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings การตั้งค่าเครือข่าย @@ -4080,11 +4635,23 @@ This is your link for group %@! สถานะเครือข่าย No comment provided by engineer. + + New + token status text + New Passcode รหัสผ่านใหม่ No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + No comment provided by engineer. + New chat No comment provided by engineer. @@ -4103,11 +4670,6 @@ This is your link for group %@! คำขอติดต่อใหม่: notification - - New database archive - ฐานข้อมูลใหม่สำหรับการเก็บถาวร - No comment provided by engineer. - New desktop app! No comment provided by engineer. @@ -4117,6 +4679,10 @@ This is your link for group %@! ชื่อที่แสดงใหม่ No comment provided by engineer. + + New events + notification + New in %@ ใหม่ใน %@ @@ -4141,6 +4707,10 @@ This is your link for group %@! รหัสผ่านใหม่… No comment provided by engineer. + + New server + No comment provided by engineer. + No เลขที่ @@ -4151,6 +4721,18 @@ This is your link for group %@! ไม่มีรหัสผ่านสำหรับแอป Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected ไม่ได้เลือกผู้ติดต่อ @@ -4193,28 +4775,90 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection No comment provided by engineer. + + No permission to record speech + No comment provided by engineer. + + + No permission to record video + No comment provided by engineer. + No permission to record voice message ไม่อนุญาตให้บันทึกข้อความเสียง No comment provided by engineer. + + No push server + ในเครื่อง + No comment provided by engineer. + No received or sent files ไม่มีไฟล์ที่ได้รับหรือส่ง No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว + No comment provided by engineer. + Not compatible! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications การแจ้งเตือน @@ -4225,6 +4869,18 @@ This is your link for group %@! ปิดการแจ้งเตือน! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4246,18 +4902,13 @@ This is your link for group %@! Ok ตกลง - No comment provided by engineer. + alert button Old database ฐานข้อมูลเก่า No comment provided by engineer. - - Old database archive - คลังฐานข้อมูลเก่า - No comment provided by engineer. - One-time invitation link ลิงก์คำเชิญแบบใช้ครั้งเดียว @@ -4280,8 +4931,12 @@ Requires compatible VPN. โฮสต์หัวหอมจะไม่ถูกใช้ No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. เฉพาะอุปกรณ์ไคลเอนต์เท่านั้นที่จัดเก็บโปรไฟล์ผู้ใช้ ผู้ติดต่อ กลุ่ม และข้อความที่ส่งด้วย **การเข้ารหัส encrypt แบบ 2 ชั้น** No comment provided by engineer. @@ -4304,6 +4959,14 @@ Requires compatible VPN. เฉพาะเจ้าของกลุ่มเท่านั้นที่สามารถเปิดใช้งานข้อความเสียงได้ No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. มีเพียงคุณเท่านั้นที่สามารถแสดงปฏิกิริยาต่อข้อความได้ @@ -4356,13 +5019,17 @@ Requires compatible VPN. Open - No comment provided by engineer. + alert action Open Settings เปิดการตั้งค่า No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat เปิดแชท @@ -4373,32 +5040,38 @@ Requires compatible VPN. เปิดคอนโซลการแชท authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. + + Open link? + alert title + Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - เปิดโปรไฟล์ผู้ใช้ - authentication reason - - - Open-source protocol and code – anybody can run the servers. - โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -4415,13 +5088,22 @@ Requires compatible VPN. Or show this code No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + alert message PING count @@ -4458,6 +5140,10 @@ Requires compatible VPN. ตั้งรหัสผ่านเรียบร้อยแล้ว! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show รหัสผ่านที่จะแสดง @@ -4488,13 +5174,8 @@ Requires compatible VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น - No comment provided by engineer. - - - Periodically + + Periodic เป็นระยะๆ No comment provided by engineer. @@ -4589,11 +5270,27 @@ Error: %@ โปรดจัดเก็บรหัสผ่านอย่างปลอดภัย คุณจะไม่สามารถเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface อินเตอร์เฟซภาษาโปแลนด์ No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง @@ -4604,16 +5301,15 @@ Error: %@ เก็บข้อความที่ร่างไว้ล่าสุดพร้อมไฟล์แนบ No comment provided by engineer. - - Preset server - เซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า - No comment provided by engineer. - Preset server address ที่อยู่เซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview ดูตัวอย่าง @@ -4628,16 +5324,32 @@ Error: %@ ความเป็นส่วนตัวและความปลอดภัย No comment provided by engineer. + + Privacy for your customers. + 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 ชื่อไฟล์ส่วนตัว No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing No comment provided by engineer. @@ -4672,14 +5384,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password รหัสผ่านโปรไฟล์ @@ -4692,7 +5396,7 @@ Error: %@ Profile update will be sent to your contacts. การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4714,6 +5418,10 @@ Error: %@ ห้ามแสดงปฏิกิริยาต่อข้อความ No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. No comment provided by engineer. @@ -4775,6 +5483,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications การแจ้งเตือนแบบทันที @@ -4812,25 +5524,20 @@ Enable in *Network & servers* settings. อ่านเพิ่มเติม No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/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. - อ่านเพิ่มเติมในที่เก็บ GitHub ของเรา - 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) @@ -4949,11 +5656,23 @@ Enable in *Network & servers* settings. ลดการใช้แบตเตอรี่ No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject ปฏิเสธ reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -4979,6 +5698,10 @@ Enable in *Network & servers* settings. ลบ No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5038,6 +5761,46 @@ Enable in *Network & servers* settings. ตอบ chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required ที่จำเป็น @@ -5117,6 +5880,10 @@ Enable in *Network & servers* settings. เปิดเผย chat item action + + Review conditions + No comment provided by engineer. + Revoke ถอน @@ -5146,6 +5913,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5157,17 +5928,18 @@ Enable in *Network & servers* settings. Save บันทึก - chat item action + alert button +chat item action Save (and notify contacts) บันทึก (และแจ้งผู้ติดต่อ) - No comment provided by engineer. + alert button Save and notify contact บันทึกและแจ้งผู้ติดต่อ - No comment provided by engineer. + alert button Save and notify group members @@ -5183,21 +5955,15 @@ Enable in *Network & servers* settings. บันทึกและอัปเดตโปรไฟล์กลุ่ม No comment provided by engineer. - - Save archive - บันทึกไฟล์เก็บถาวร - No comment provided by engineer. - - - Save auto-accept settings - บันทึกการตั้งค่าการยอมรับอัตโนมัติ - No comment provided by engineer. - Save group profile บันทึกโปรไฟล์กลุ่ม No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat บันทึกรหัสผ่านและเปิดแชท @@ -5211,7 +5977,7 @@ Enable in *Network & servers* settings. Save preferences? บันทึกการตั้งค่า? - No comment provided by engineer. + alert title Save profile password @@ -5226,18 +5992,17 @@ Enable in *Network & servers* settings. Save servers? บันทึกเซิร์ฟเวอร์? - No comment provided by engineer. - - - Save settings? - บันทึกการตั้งค่า? - No comment provided by engineer. + alert title Save welcome message? บันทึกข้อความต้อนรับ? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5255,6 +6020,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5328,6 +6097,10 @@ Enable in *Network & servers* settings. เลือก chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5411,9 +6184,8 @@ Enable in *Network & servers* settings. ส่งการแจ้งเตือน No comment provided by engineer. - - Send notifications: - ส่งการแจ้งเตือน: + + Send private reports No comment provided by engineer. @@ -5438,7 +6210,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. ผู้ส่งยกเลิกการโอนไฟล์ - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5528,6 +6300,14 @@ Enable in *Network & servers* settings. Sent via proxy No comment provided by engineer. + + Server + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5540,6 +6320,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน @@ -5589,6 +6381,10 @@ Enable in *Network & servers* settings. ตั้ง 1 วัน No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… ตั้งชื่อผู้ติดต่อ… @@ -5608,6 +6404,10 @@ Enable in *Network & servers* settings. ตั้งแทนการรับรองความถูกต้องของระบบ No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode ตั้งรหัสผ่าน @@ -5637,6 +6437,10 @@ Enable in *Network & servers* settings. การตั้งค่า No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5644,22 +6448,35 @@ Enable in *Network & servers* settings. Share แชร์ - chat item action + alert action +chat item action Share 1-time link แชร์ลิงก์แบบใช้ครั้งเดียว No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address แชร์ที่อยู่ No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? แชร์ที่อยู่กับผู้ติดต่อ? - No comment provided by engineer. + alert title Share from other apps. @@ -5670,6 +6487,10 @@ Enable in *Network & servers* settings. แชร์ลิงก์ No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -5683,6 +6504,10 @@ Enable in *Network & servers* settings. แชร์กับผู้ติดต่อ No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -5732,6 +6557,10 @@ Enable in *Network & servers* settings. ที่อยู่ SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. ความปลอดภัยของ SimpleX Chat ได้รับการตรวจสอบโดย Trail of Bits @@ -5762,6 +6591,18 @@ Enable in *Network & servers* settings. ที่อยู่ SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address ที่อยู่ติดต่อ SimpleX @@ -5782,8 +6623,8 @@ Enable in *Network & servers* settings. ลิงก์ SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -5795,6 +6636,10 @@ Enable in *Network & servers* settings. คำเชิญ SimpleX แบบครั้งเดียว simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode No comment provided by engineer. @@ -5821,6 +6666,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -5834,11 +6683,21 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody ใครบางคน notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. No comment provided by engineer. @@ -5879,11 +6738,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - หยุดการแชทเพื่อเปิดใช้งานการดำเนินการกับฐานข้อมูล - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. หยุดแชทเพื่อส่งออก นำเข้า หรือลบฐานข้อมูลแชท คุณจะไม่สามารถรับและส่งข้อความได้ในขณะที่การแชทหยุดลง @@ -5912,17 +6766,21 @@ Enable in *Network & servers* settings. Stop sharing หยุดแชร์ - No comment provided by engineer. + alert action Stop sharing address? หยุดแชร์ที่อยู่ไหม? - No comment provided by engineer. + alert title Stopping chat No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong blur media @@ -5949,6 +6807,14 @@ Enable in *Network & servers* settings. สนับสนุน SimpleX แชท No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System ระบบ @@ -5968,6 +6834,10 @@ Enable in *Network & servers* settings. หมดเวลาการเชื่อมต่อ TCP No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -5983,11 +6853,19 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture ถ่ายภาพ No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button แตะปุ่ม @@ -6022,13 +6900,17 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + file error alert title Test failed at step %@. การทดสอบล้มเหลวในขั้นตอน %@ server test failure + + Test notifications + No comment provided by engineer. + Test server เซิร์ฟเวอร์ทดสอบ @@ -6042,7 +6924,7 @@ Enable in *Network & servers* settings. Tests failed! การทดสอบล้มเหลว! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6059,11 +6941,6 @@ Enable in *Network & servers* settings. ขอบคุณผู้ใช้ – มีส่วนร่วมผ่าน Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6077,6 +6954,10 @@ It can happen because of some bug or when the connection is compromised.แอปสามารถแจ้งให้คุณทราบเมื่อคุณได้รับข้อความหรือคำขอติดต่อ - โปรดเปิดการตั้งค่าเพื่อเปิดใช้งาน No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6090,6 +6971,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! การเชื่อมต่อที่คุณยอมรับจะถูกยกเลิก! @@ -6110,6 +6995,11 @@ It can happen because of some bug or when the connection is compromised.encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้! No comment provided by engineer. + + The future of messaging + การส่งข้อความส่วนตัวรุ่นต่อไป + No comment provided by engineer. + The hash of the previous message is different. แฮชของข้อความก่อนหน้านี้แตกต่างกัน @@ -6133,19 +7023,17 @@ 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. - - The next generation of private messaging - การส่งข้อความส่วนตัวรุ่นต่อไป - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้ No comment provided by engineer. - - The profile is only shared with your contacts. - โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6163,14 +7051,26 @@ It can happen because of some bug or when the connection is compromised.เซิร์ฟเวอร์สำหรับการเชื่อมต่อใหม่ของโปรไฟล์การแชทปัจจุบันของคุณ **%@** No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. การตั้งค่าเหล่านี้ใช้สำหรับโปรไฟล์ปัจจุบันของคุณ **%@** @@ -6190,6 +7090,10 @@ It can happen because of some bug or when the connection is compromised.การดำเนินการนี้ไม่สามารถเลิกทำได้ - ข้อความที่ส่งและรับก่อนหน้าที่เลือกไว้จะถูกลบ อาจใช้เวลาหลายนาที No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. การดำเนินการนี้ไม่สามารถยกเลิกได้ - โปรไฟล์ ผู้ติดต่อ ข้อความ และไฟล์ของคุณจะสูญหายไปอย่างถาวร @@ -6228,10 +7132,18 @@ It can happen because of some bug or when the connection is compromised.This is your own one-time link! 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. + No comment provided by engineer. + 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. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. การตั้งค่านี้ใช้กับข้อความในโปรไฟล์แชทปัจจุบันของคุณ **%@** @@ -6260,9 +7172,8 @@ It can happen because of some bug or when the connection is compromised.เพื่อสร้างการเชื่อมต่อใหม่ No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6281,6 +7192,23 @@ You will be prompted to complete authentication before this feature is enabled.< คุณจะได้รับแจ้งให้ยืนยันตัวตนให้เสร็จสมบูรณ์ก่อนที่จะเปิดใช้งานคุณลักษณะนี้ No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. ในการบันทึกข้อความเสียง โปรดให้สิทธิ์ในการใช้ไมโครโฟน @@ -6291,11 +7219,19 @@ You will be prompted to complete authentication before this feature is enabled.< หากต้องการเปิดเผยโปรไฟล์ที่ซ่อนอยู่ของคุณ ให้ป้อนรหัสผ่านแบบเต็มในช่องค้นหาในหน้า **โปรไฟล์แชทของคุณ** No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. เพื่อรองรับการแจ้งเตือนแบบทันที ฐานข้อมูลการแชทจะต้องได้รับการโยกย้าย No comment provided by engineer. + + 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. ในการตรวจสอบการเข้ารหัสแบบ encrypt จากต้นจนจบ กับผู้ติดต่อของคุณ ให้เปรียบเทียบ (หรือสแกน) รหัสบนอุปกรณ์ของคุณ @@ -6309,6 +7245,10 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle incognito when connecting. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity No comment provided by engineer. @@ -6375,6 +7315,10 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state สถานะการย้ายข้อมูลที่ไม่คาดคิด @@ -6422,7 +7366,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6457,13 +7401,17 @@ To connect, please ask your contact to create another connection link and check Unmute เปิดเสียง - swipe action + notification label action Unread เปลี่ยนเป็นยังไม่ได้อ่าน swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -6487,6 +7435,10 @@ To connect, please ask your contact to create another connection link and check Update settings? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. การอัปเดตการตั้งค่าจะเชื่อมต่อไคลเอนต์กับเซิร์ฟเวอร์ทั้งหมดอีกครั้ง @@ -6522,16 +7474,32 @@ To connect, please ask your contact to create another connection link and check Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts ใช้โฮสต์ .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? ใช้เซิร์ฟเวอร์ SimpleX Chat ไหม? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat ใช้แชท @@ -6541,6 +7509,14 @@ To connect, please ask your contact to create another connection link and check Use current profile No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections ใช้สำหรับการเชื่อมต่อใหม่ @@ -6576,6 +7552,14 @@ To connect, please ask your contact to create another connection link and check ใช้เซิร์ฟเวอร์ No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6584,15 +7568,18 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. - - User profile - โปรไฟล์ผู้ใช้ + + Use web port No comment provided by engineer. User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. กำลังใช้เซิร์ฟเวอร์ SimpleX Chat อยู่ @@ -6657,11 +7644,19 @@ To connect, please ask your contact to create another connection link and check วิดีโอและไฟล์สูงสุด 1gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code ดูรหัสความปลอดภัย No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -6676,8 +7671,8 @@ To connect, please ask your contact to create another connection link and check ห้ามส่งข้อความเสียงในแชทนี้ No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. ข้อความเสียงเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -6764,9 +7759,8 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - เมื่อมีคนขอเชื่อมต่อ คุณสามารถยอมรับหรือปฏิเสธได้ + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -6805,7 +7799,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -6829,11 +7823,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - You - คุณ - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. @@ -6858,6 +7847,10 @@ To connect, please ask your contact to create another connection link and check คุณได้เชื่อมต่อกับ %@ แล้ว No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. @@ -6910,6 +7903,10 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure servers via settings. + No comment provided by engineer. + You can create it later คุณสามารถสร้างได้ในภายหลัง @@ -6947,6 +7944,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. คุณสามารถตั้งค่าแสดงตัวอย่างการแจ้งเตือนบนหน้าจอล็อคผ่านการตั้งค่า @@ -6962,11 +7963,6 @@ Repeat join request? คุณสามารถแบ่งปันที่อยู่นี้กับผู้ติดต่อของคุณเพื่อให้พวกเขาเชื่อมต่อกับ **%@** No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - คุณสามารถแชร์ที่อยู่ของคุณเป็นลิงก์หรือรหัสคิวอาร์ - ใคร ๆ ก็สามารถเชื่อมต่อกับคุณได้ - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app คุณสามารถเริ่มแชทผ่านการตั้งค่าแอป / ฐานข้อมูล หรือโดยการรีสตาร์ทแอป @@ -6988,23 +7984,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! คุณไม่สามารถส่งข้อความได้! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - คุณควบคุมผ่านเซิร์ฟเวอร์ **เพื่อรับ** ข้อความผู้ติดต่อของคุณ - เซิร์ฟเวอร์ที่คุณใช้เพื่อส่งข้อความถึงพวกเขา - 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 already requested connection via this address! No comment provided by engineer. @@ -7065,6 +8061,10 @@ Repeat connection request? คุณส่งคำเชิญเข้าร่วมกลุ่มแล้ว No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! คุณจะเชื่อมต่อกับกลุ่มเมื่ออุปกรณ์โฮสต์ของกลุ่มออนไลน์อยู่ โปรดรอหรือตรวจสอบภายหลัง! @@ -7098,6 +8098,10 @@ Repeat connection request? คุณจะยังได้รับสายเรียกเข้าและการแจ้งเตือนจากโปรไฟล์ที่ปิดเสียงเมื่อโปรไฟล์ของเขามีการใช้งาน No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. คุณจะหยุดได้รับข้อความจากกลุ่มนี้ ประวัติการแชทจะถูกรักษาไว้ @@ -7118,31 +8122,16 @@ Repeat connection request? คุณกำลังใช้โปรไฟล์ที่ไม่ระบุตัวตนสำหรับกลุ่มนี้ - ไม่อนุญาตให้เชิญผู้ติดต่อเพื่อป้องกันการแชร์โปรไฟล์หลักของคุณ No comment provided by engineer. - - Your %@ servers - เซิร์ฟเวอร์ %@ ของคุณ - No comment provided by engineer. - Your ICE servers เซิร์ฟเวอร์ ICE ของคุณ No comment provided by engineer. - - Your SMP servers - เซิร์ฟเวอร์ SMP ของคุณ - No comment provided by engineer. - Your SimpleX address ที่อยู่ SimpleX ของคุณ No comment provided by engineer. - - Your XFTP servers - เซิร์ฟเวอร์ XFTP ของคุณ - No comment provided by engineer. - Your calls การโทรของคุณ @@ -7158,11 +8147,19 @@ Repeat connection request? ฐานข้อมูลการแชทของคุณไม่ได้ถูก encrypt - ตั้งรหัสผ่านเพื่อ encrypt No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles โปรไฟล์แชทของคุณ No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). ผู้ติดต่อของคุณส่งไฟล์ที่ใหญ่กว่าขนาดสูงสุดที่รองรับในปัจจุบัน (%@) @@ -7178,6 +8175,10 @@ Repeat connection request? ผู้ติดต่อของคุณจะยังคงเชื่อมต่ออยู่ 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. ฐานข้อมูลแชทปัจจุบันของคุณจะถูกลบและแทนที่ด้วยฐานข้อมูลที่นำเข้า @@ -7206,33 +8207,34 @@ Repeat connection request? Your profile **%@** will be shared. 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 only shared with your contacts. + โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ 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 random profile โปรไฟล์แบบสุ่มของคุณ No comment provided by engineer. - - Your server - เซิร์ฟเวอร์ของคุณ - No comment provided by engineer. - Your server address ที่อยู่เซิร์ฟเวอร์ของคุณ No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings การตั้งค่าของคุณ @@ -7273,6 +8275,10 @@ SimpleX servers cannot see your profile. รับสายแล้ว call status + + accepted invitation + chat list item title + admin ผู้ดูแลระบบ @@ -7305,6 +8311,10 @@ SimpleX servers cannot see your profile. and %lld other events No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts No comment provided by engineer. @@ -7338,7 +8348,8 @@ SimpleX servers cannot see your profile. blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7451,7 +8462,7 @@ SimpleX servers cannot see your profile. connecting… กำลังเชื่อมต่อ… - chat list item title + No comment provided by engineer. connection established @@ -7504,7 +8515,8 @@ SimpleX servers cannot see your profile. default (%@) ค่าเริ่มต้น (%@) - pref value + delete after time +pref value default (no) @@ -7628,10 +8640,6 @@ SimpleX servers cannot see your profile. ผิดพลาด No comment provided by engineer. - - event happened - No comment provided by engineer. - expired No comment provided by engineer. @@ -7796,19 +8804,19 @@ SimpleX servers cannot see your profile. กลั่นกรองโดย %@ marked deleted chat item preview text + + moderator + member role + months เดือน time unit - - mute - No comment provided by engineer. - never ไม่เคย - No comment provided by engineer. + delete after time new message @@ -7839,8 +8847,8 @@ SimpleX servers cannot see your profile. off ปิด enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -7879,6 +8887,14 @@ SimpleX servers cannot see your profile. เพื่อนต่อเพื่อน No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -7893,6 +8909,10 @@ SimpleX servers cannot see your profile. ได้รับการยืนยัน… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call สายถูกปฏิเสธ @@ -7921,6 +8941,10 @@ SimpleX servers cannot see your profile. ลบคุณออกแล้ว rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. @@ -8007,10 +9031,6 @@ last received msg: %2$@ unknown status No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected No comment provided by engineer. @@ -8167,7 +9187,7 @@ last received msg: %2$@
- +
@@ -8203,7 +9223,7 @@ last received msg: %2$@
- +
@@ -8223,9 +9243,36 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
@@ -8244,7 +9291,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/th.xcloc/contents.json b/apps/ios/SimpleX Localizations/th.xcloc/contents.json index 4562ab8385..ee6ee63ea9 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/th.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "th", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 054f65110f..bbee40c2b9 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (kopyalanabilir) @@ -127,6 +100,16 @@ %@ onaylandı No comment provided by engineer. + + %@ server + %@ sunucu + No comment provided by engineer. + + + %@ servers + %@ sunucular + No comment provided by engineer. + %@ uploaded %@ yüklendi @@ -137,6 +120,11 @@ %@ bağlanmak istiyor! notification title + + %1$@, %2$@ + %1$@,%2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ ve %lld üyeleri @@ -157,11 +145,36 @@ %d gün time interval + + %d file(s) are still being downloaded. + %d dosyası(ları) hala indiriliyor. + forward confirmation reason + + + %d file(s) failed to download. + %d dosyası(ları) indirilemedi. + forward confirmation reason + + + %d file(s) were deleted. + %d dosyası(ları) silindi. + forward confirmation reason + + + %d file(s) were not downloaded. + %d dosyası(ları) indirilmedi. + forward confirmation reason + %d hours %d saat time interval + + %d messages not forwarded + %d mesajı iletilmeyedi + alert title + %d min %d dakika @@ -177,6 +190,10 @@ %d saniye time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d okunmamış mesaj(lar) @@ -247,11 +264,6 @@ %lld yeni arayüz dilleri No comment provided by engineer. - - %lld second(s) - %lld saniye - No comment provided by engineer. - %lld seconds %lld saniye @@ -302,11 +314,6 @@ %u mesajlar atlandı. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (yeni) @@ -317,33 +324,23 @@ (bu cihaz v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **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. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Yeni kişi ekleyin**: tek seferlik QR Kodunuzu oluşturmak veya kişisel ulaşım bilgileri bağlantısı için. - No comment provided by engineer. - **Create group**: to create a new group. **Grup oluştur**: yeni bir grup oluşturmak için. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **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. **Daha gizli**: her 20 dakikada yeni mesajlar için kontrol et. Cihaz jetonu SimpleX Chat sunucusuyla paylaşılacak, ama ne kadar kişi veya mesaja sahip olduğun paylaşılmayacak. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **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. **En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır). No comment provided by engineer. @@ -357,11 +354,16 @@ **Lütfen aklınızda bulunsun**: eğer parolanızı kaybederseniz parolanızı değiştirme veya geri kurtarma ihtimaliniz YOKTUR. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **Önerilen**: cihaz tokeni ve bildirimler SimpleX Chat bildirim sunucularına gönderilir, ama mesajın içeriği, boyutu veya kimden geldiği gönderilmez. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + edindiğiniz bağlantı aracılığıyla bağlanmak için **Linki tarayın/yapıştırın**. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Dikkat**: Anında iletilen bildirimlere Anahtar Zinciri'nde kaydedilmiş parola gereklidir. @@ -387,11 +389,6 @@ \*kalın* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +425,6 @@ - düzenleme geçmişi. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 saniye @@ -446,7 +438,8 @@ 1 day 1 gün - time interval + delete after time +time interval 1 hour @@ -461,12 +454,28 @@ 1 month 1 ay - time interval + delete after time +time interval 1 week 1 hafta - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + tek kullanımlık bağlantı + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Tek kullanımlık bağlantı *sadece bir kişi ile* kullanılabilir - kişiyle veya uygulama içinden paylaş. + No comment provided by engineer. 5 minutes @@ -483,11 +492,6 @@ 30 saniye No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,31 +541,32 @@ Adres değişimi iptal edilsin mi? No comment provided by engineer. - - About SimpleX - SimpleX Hakkında - No comment provided by engineer. - About SimpleX Chat SimpleX Chat hakkında No comment provided by engineer. - - About SimpleX address - SimpleX Chat adresi hakkında + + About operators + Operatörler hakkında No comment provided by engineer. Accent + Ana renk No comment provided by engineer. Accept Kabul et accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Koşulları kabul et + No comment provided by engineer. Accept connection request? @@ -577,18 +582,30 @@ Accept incognito Takma adla kabul et accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Kabul edilmiş koşullar + No comment provided by engineer. Acknowledged + Onaylandı No comment provided by engineer. Acknowledgement errors + Onay hataları No comment provided by engineer. + + Active + token status text + Active connections + Aktif bağlantılar No comment provided by engineer. @@ -596,14 +613,13 @@ 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 contact - Kişi ekle + + Add friends + Arkadaş ekle No comment provided by engineer. - - Add preset servers - Önceden ayarlanmış sunucu ekle + + Add list No comment provided by engineer. @@ -621,26 +637,53 @@ Karekod taratarak sunucuları ekleyin. No comment provided by engineer. + + Add team members + Takım üyesi ekle + No comment provided by engineer. + Add to another device Başka bir cihaza ekle No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message Karşılama mesajı ekleyin No comment provided by engineer. + + Add your team members to the conversations. + Takım üyelerini konuşmalara ekle. + No comment provided by engineer. + + + Added media & file servers + medya ve dosya sunucuları eklendi + No comment provided by engineer. + + + Added message servers + Mesaj sunucuları eklendi + No comment provided by engineer. + Additional accent + Ek ana renk No comment provided by engineer. Additional accent 2 + Ek vurgu 2 No comment provided by engineer. Additional secondary + Ek ikincil renk No comment provided by engineer. @@ -653,6 +696,16 @@ Adres değişikliği iptal edilecek. Eski alıcı adresi kullanılacaktır. No comment provided by engineer. + + Address or 1-time link? + adres mi yoksa tek kullanımlık bağlantı mı? + No comment provided by engineer. + + + Address settings + Adres seçenekleri + No comment provided by engineer. + Admins can block a member for all. Yöneticiler bir üyeyi tamamen engelleyebilirler. @@ -670,6 +723,11 @@ Advanced settings + Gelişmiş ayarlar + No comment provided by engineer. + + + All No comment provided by engineer. @@ -682,13 +740,18 @@ Tüm konuşmalar ve mesajlar silinecektir. Bu, geri alınamaz! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. Kullanıldığında bütün veriler silinir. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. + Tüm veriler cihazınıza özeldir. No comment provided by engineer. @@ -696,6 +759,11 @@ Tüm grup üyeleri bağlı kalacaktır. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Bütün mesajlar ve dosyalar **uçtan-uca şifrelemeli** gönderilir, doğrudan mesajlarda kuantum güvenlik ile birlikte. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Tüm mesajlar silinecektir - bu geri alınamaz! @@ -713,6 +781,15 @@ All profiles + Tüm Profiller + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -742,6 +819,7 @@ Allow calls? + Aramalara izin verilsin mi ? No comment provided by engineer. @@ -781,6 +859,7 @@ Allow sharing + Paylaşıma izin ver No comment provided by engineer. @@ -788,6 +867,10 @@ Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde) No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. SimpleX bağlantıları göndilmesine izin ver. @@ -868,11 +951,20 @@ Verilen adla boş bir sohbet profili oluşturulur ve uygulama her zamanki gibi açılır. No comment provided by engineer. + + Another reason + report reason + 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ü: %@ @@ -888,6 +980,10 @@ Uygulama yerel dosyaları şifreler (videolar dışında). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon Uygulama simgesi @@ -903,6 +999,11 @@ Uygulama parolası kendi kendini imha eden parolayla değiştirildi. No comment provided by engineer. + + App session + Uygulama oturumu + No comment provided by engineer. + App version Uygulama sürümü @@ -925,6 +1026,19 @@ Apply to + Şuna uygula + No comment provided by engineer. + + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? No comment provided by engineer. @@ -934,10 +1048,24 @@ Archive contacts to chat later. + Daha sonra görüşmek için kişileri arşivleyin. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts + Arşivli kişiler No comment provided by engineer. @@ -1005,6 +1133,11 @@ Fotoğrafları otomatik kabul et No comment provided by engineer. + + Auto-accept settings + Ayarları otomatik olarak kabul et + alert title + Back Geri @@ -1012,6 +1145,7 @@ Background + Arka plan No comment provided by engineer. @@ -1029,11 +1163,25 @@ Kötü mesaj karması No comment provided by engineer. + + Better calls + Daha iyi aramalar + No comment provided by engineer. + Better groups Daha iyi gruplar No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + Daha iyi mesaj tarihleri. + No comment provided by engineer. + Better messages Daha iyi mesajlar @@ -1041,10 +1189,31 @@ Better networking + Daha iyi ağ oluşturma + No comment provided by engineer. + + + Better notifications + Daha iyi bildirimler + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + Daha iyi güvenlik ✅ + No comment provided by engineer. + + + Better user experience + Daha iyi kullanıcı deneyimi No comment provided by engineer. Black + Siyah No comment provided by engineer. @@ -1084,10 +1253,12 @@ Blur for better privacy. + Daha iyi gizlilik için bulanıklaştır. No comment provided by engineer. Blur media + Medyayı bulanıklaştır No comment provided by engineer. @@ -1120,11 +1291,31 @@ 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. + + Business address + İş adresi + No comment provided by engineer. + + + Business chats + İş konuşmaları + No comment provided by engineer. + + + Businesses + 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: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Arama çoktan bitti! @@ -1137,6 +1328,7 @@ Calls prohibited! + Aramalara izin verilmiyor! No comment provided by engineer. @@ -1146,10 +1338,12 @@ Can't call contact + Kişi aranamıyor No comment provided by engineer. Can't call member + Üye aranamaz No comment provided by engineer. @@ -1164,12 +1358,14 @@ Can't message member + Üyeye mesaj gönderilemiyor No comment provided by engineer. Cancel İptal et - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1183,12 +1379,13 @@ Cannot forward message + Mesaj iletilemiyor No comment provided by engineer. Cannot receive file Dosya alınamıyor - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1205,6 +1402,15 @@ Değiştir No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + Sohbet profillerini değiştir + authentication reason + Change database passphrase? Veritabanı parolasını değiştir? @@ -1249,15 +1455,26 @@ Change self-destruct passcode Kendini yok eden parolayı değiştir authentication reason - set passcode view +set passcode view - - Chat archive - Sohbet arşivi + + Chat + Sohbet + No comment provided by engineer. + + + Chat already exists + Sohbet zaten mevcut + No comment provided by engineer. + + + Chat already exists! + Sohbet zaten mevcut! No comment provided by engineer. Chat colors + Sohbet renkleri No comment provided by engineer. @@ -1277,6 +1494,7 @@ Chat database exported + Veritabanı dışa aktarıldı No comment provided by engineer. @@ -1301,6 +1519,7 @@ Chat list + Sohbet listesi No comment provided by engineer. @@ -1313,8 +1532,29 @@ Sohbet tercihleri No comment provided by engineer. + + Chat preferences were changed. + Sohbet tercihleri değiştirildi. + alert message + + + Chat profile + Kullanıcı profili + No comment provided by engineer. + Chat theme + Sohbet teması + No comment provided by engineer. + + + Chat will be deleted for all members - this cannot be undone! + Sohbet bütün üyeler için silinecek - bu geri alınamaz! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + Sohbet senden silinecek - bu geri alınamaz! No comment provided by engineer. @@ -1322,10 +1562,20 @@ Sohbetler No comment provided by engineer. + + Check messages every 20 min. + Her 20 dakikada mesajları kontrol et. + No comment provided by engineer. + + + Check messages when allowed. + İzin verildiğinde mesajları kontrol et. + No comment provided by engineer. + Check server address and try again. Sunucu adresini kontrol edip tekrar deneyin. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1349,14 +1599,17 @@ Chunks deleted + Parçalar silindi No comment provided by engineer. Chunks downloaded + Parçalar indirildi No comment provided by engineer. Chunks uploaded + Parçalar yüklendi No comment provided by engineer. @@ -1374,6 +1627,14 @@ Sohbet temizlensin mi? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? Gizli notlar temizlensin mi? @@ -1386,12 +1647,18 @@ Color chats with the new themes. + Yeni temalarla renkli sohbetler. No comment provided by engineer. Color mode + Renk modu No comment provided by engineer. + + Community guidelines violation + report reason + Compare file Dosya karşılaştır @@ -1404,6 +1671,42 @@ Completed + Tamamlandı + No comment provided by engineer. + + + Conditions accepted on: %@. + Şuradaki koşullar kabul edildi: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Koşullar operatör(ler) için kabul edildi: **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Koşullar çoktan operatör(ler) tarafından kabul edildi: **%@**. + No comment provided by engineer. + + + Conditions of use + Kullanım koşulları + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Koşullar bu operatör(ler) için kabul edilecektir: **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Koşullar şu tarihte kabul edilecektir: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Koşullar etkin operatörler için şu tarihte otomatik olarak kabul edilecektir: %@. No comment provided by engineer. @@ -1411,8 +1714,8 @@ ICE sunucularını ayarla No comment provided by engineer. - - Configured %@ servers + + Configure server operators No comment provided by engineer. @@ -1427,6 +1730,7 @@ Confirm contact deletion? + Kişiyi silmek istediğinizden emin misiniz ? No comment provided by engineer. @@ -1464,6 +1768,10 @@ Yüklemeyi onayla No comment provided by engineer. + + Confirmed + token status text + Connect Bağlan @@ -1486,6 +1794,7 @@ Connect to your friends faster. + Arkadaşlarınıza daha hızlı bağlanın. No comment provided by engineer. @@ -1529,6 +1838,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connected + Bağlandı No comment provided by engineer. @@ -1538,15 +1848,17 @@ Bu senin kendi tek kullanımlık bağlantın! Connected servers + Bağlı sunucular No comment provided by engineer. Connected to desktop - Bilgisayara bağlanıldı + Masaüstüne bağlandı No comment provided by engineer. Connecting + Bağlanıyor No comment provided by engineer. @@ -1561,6 +1873,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connecting to contact, please wait or check later! + Kişiye bağlanılıyor, lütfen bekleyin ya da daha sonra kontrol edin! No comment provided by engineer. @@ -1575,6 +1888,11 @@ Bu senin kendi tek kullanımlık bağlantın! Connection and servers status. + Bağlantı ve sunucuların durumu. + No comment provided by engineer. + + + Connection blocked No comment provided by engineer. @@ -1587,8 +1905,18 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı hatası (DOĞRULAMA) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications + Bağlantı bildirimleri No comment provided by engineer. @@ -1596,6 +1924,15 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı daveti gönderildi! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + Bağlantı güvenliği + No comment provided by engineer. + Connection terminated Bağlantı sonlandırılmış @@ -1608,10 +1945,12 @@ Bu senin kendi tek kullanımlık bağlantın! Connection with desktop stopped + Masaüstü ile bağlantı durduruldu No comment provided by engineer. Connections + Bağlantılar No comment provided by engineer. @@ -1626,6 +1965,7 @@ Bu senin kendi tek kullanımlık bağlantın! Contact deleted! + Kişiler silindi! No comment provided by engineer. @@ -1640,6 +1980,7 @@ Bu senin kendi tek kullanımlık bağlantın! Contact is deleted. + Kişi silindi. No comment provided by engineer. @@ -1654,6 +1995,7 @@ Bu senin kendi tek kullanımlık bağlantın! Contact will be deleted - this cannot be undone! + Kişiler silinecek - bu geri alınamaz ! No comment provided by engineer. @@ -1666,6 +2008,10 @@ Bu senin kendi tek kullanımlık bağlantın! Kişiler silinmesi için mesajları işaretleyebilir; onları görüntüleyebilirsin. No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue Devam et @@ -1673,6 +2019,7 @@ Bu senin kendi tek kullanımlık bağlantın! Conversation deleted! + Sohbet silindi! No comment provided by engineer. @@ -1682,6 +2029,7 @@ Bu senin kendi tek kullanımlık bağlantın! Copy error + Kopyalama hatası No comment provided by engineer. @@ -1689,6 +2037,11 @@ Bu senin kendi tek kullanımlık bağlantın! Çekirdek sürümü: v%@ No comment provided by engineer. + + Corner + Köşeleri yuvarlama + No comment provided by engineer. + Correct name to %@? İsim %@ olarak düzeltilsin mi? @@ -1699,6 +2052,11 @@ Bu senin kendi tek kullanımlık bağlantın! Oluştur No comment provided by engineer. + + Create 1-time link + Tek kullanımlık bağlantı oluştur + No comment provided by engineer. + Create SimpleX address SimpleX adresi oluştur @@ -1709,11 +2067,6 @@ Bu senin kendi tek kullanımlık bağlantın! Rasgele profil kullanarak grup oluştur. No comment provided by engineer. - - Create an address to let people connect with you. - İnsanların seninle bağlanması için bir adres oluştur. - No comment provided by engineer. - Create file Dosya oluştur @@ -1734,6 +2087,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı oluştur No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 [bilgisayar uygulaması] nda yeni bir profil oluştur(https://simplex.chat/downloads/). 💻 @@ -1761,6 +2118,7 @@ Bu senin kendi tek kullanımlık bağlantın! Created + Yaratıldı No comment provided by engineer. @@ -1773,11 +2131,6 @@ Bu senin kendi tek kullanımlık bağlantın! Şurada oluşturuldu: %@ copied message info - - Created on %@ - %@ de oluşturuldu - No comment provided by engineer. - Creating archive link Arşiv bağlantısı oluşturuluyor @@ -1793,6 +2146,11 @@ Bu senin kendi tek kullanımlık bağlantın! Şu anki şifre No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Şu anki koşulların yazısı yüklenemiyor, bu bağlantıdan koşullara inceleyebilirsin: + No comment provided by engineer. + Current passphrase… Şu anki parola… @@ -1800,6 +2158,7 @@ Bu senin kendi tek kullanımlık bağlantın! Current profile + Aktif profil No comment provided by engineer. @@ -1812,8 +2171,14 @@ Bu senin kendi tek kullanımlık bağlantın! Özel saat No comment provided by engineer. + + Customizable message shape. + Özelleştirilebilir mesaj şekli. + No comment provided by engineer. + Customize theme + Renk temalarını kişiselleştir No comment provided by engineer. @@ -1823,6 +2188,7 @@ Bu senin kendi tek kullanımlık bağlantın! Dark mode colors + Karanlık mod renkleri No comment provided by engineer. @@ -1941,11 +2307,12 @@ Bu senin kendi tek kullanımlık bağlantın! Delete Sil - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? + Üyelerin %lld mesajları silinsin mi? No comment provided by engineer. @@ -1978,14 +2345,13 @@ Bu senin kendi tek kullanımlık bağlantın! Sil ve kişiye bildir No comment provided by engineer. - - Delete archive - Arşivi sil + + Delete chat + Sohbeti sil No comment provided by engineer. - - Delete chat archive? - Sohbet arşivi silinsin mi? + + Delete chat messages from your device. No comment provided by engineer. @@ -1998,6 +2364,11 @@ Bu senin kendi tek kullanımlık bağlantın! Sohbet profili silinsin mi? No comment provided by engineer. + + Delete chat? + Sohbet silinsin mi? + No comment provided by engineer. + Delete connection Bağlantıyı sil @@ -2010,6 +2381,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete contact? + Kişiyi sil? No comment provided by engineer. @@ -2072,6 +2444,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı silinsin mi? No comment provided by engineer. + + Delete list? + alert title + Delete member message? Kişinin mesajı silinsin mi? @@ -2085,7 +2461,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete messages Mesajları sil - No comment provided by engineer. + alert button Delete messages after @@ -2102,6 +2478,11 @@ Bu senin kendi tek kullanımlık bağlantın! Eski veritabanı silinsin mi? No comment provided by engineer. + + Delete or moderate up to 200 messages. + 200'e kadar mesajı silin veya düzenleyin. + No comment provided by engineer. + Delete pending connection? Bekleyen bağlantı silinsin mi? @@ -2117,8 +2498,13 @@ Bu senin kendi tek kullanımlık bağlantın! Sırayı sil server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. + Tek seferde en fazla 20 mesaj silin. No comment provided by engineer. @@ -2128,10 +2514,12 @@ Bu senin kendi tek kullanımlık bağlantın! Delete without notification + Bildirim göndermeden sil No comment provided by engineer. Deleted + Silindi No comment provided by engineer. @@ -2146,6 +2534,11 @@ Bu senin kendi tek kullanımlık bağlantın! Deletion errors + Silme hatası + No comment provided by engineer. + + + Delivered even when Apple drops them. No comment provided by engineer. @@ -2185,6 +2578,7 @@ Bu senin kendi tek kullanımlık bağlantın! Destination server address of %@ is incompatible with forwarding server %@ settings. + Hedef sunucu adresi %@, yönlendirme sunucusu %@ ayarlarıyla uyumlu değil. No comment provided by engineer. @@ -2194,14 +2588,17 @@ Bu senin kendi tek kullanımlık bağlantın! Destination server version of %@ is incompatible with forwarding server %@. + Hedef sunucu %@ sürümü, yönlendirme sunucusu %@ ile uyumlu değil. No comment provided by engineer. Detailed statistics + Detaylı istatistikler No comment provided by engineer. Details + Detaylar No comment provided by engineer. @@ -2211,6 +2608,7 @@ Bu senin kendi tek kullanımlık bağlantın! Developer options + Geliştirici seçenekleri No comment provided by engineer. @@ -2243,8 +2641,13 @@ Bu senin kendi tek kullanımlık bağlantın! Doğrudan mesajlar chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited in this chat. + Üyeler arası doğrudan mesajlar bu sohbette yasaktır. + No comment provided by engineer. + + + Direct messages between members are prohibited. Bu grupta üyeler arasında direkt mesajlaşma yasaktır. No comment provided by engineer. @@ -2258,6 +2661,14 @@ Bu senin kendi tek kullanımlık bağlantın! SimpleX Kilidini devre dışı bırak authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all Herkes için devre dışı bırak @@ -2265,6 +2676,7 @@ Bu senin kendi tek kullanımlık bağlantın! Disabled + Devre dışı No comment provided by engineer. @@ -2282,8 +2694,8 @@ Bu senin kendi tek kullanımlık bağlantın! Kaybolan mesajlar bu sohbette yasaklanmış. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Kaybolan mesajlar bu grupta yasaklanmış. No comment provided by engineer. @@ -2342,6 +2754,15 @@ Bu senin kendi tek kullanımlık bağlantın! Yeni üyelere geçmişi gönderme. No comment provided by engineer. + + Do not use credentials with proxy. + Kimlik bilgilerini proxy ile kullanmayın. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address Adres oluşturma @@ -2352,11 +2773,19 @@ Bu senin kendi tek kullanımlık bağlantın! Etkinleştirme No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again Yeniden gösterme No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat Sürüm düşür ve sohbeti aç @@ -2365,10 +2794,12 @@ Bu senin kendi tek kullanımlık bağlantın! Download İndir - chat item action + alert button +chat item action Download errors + İndirme hataları No comment provided by engineer. @@ -2381,12 +2812,19 @@ Bu senin kendi tek kullanımlık bağlantın! Dosya indir server test step + + Download files + Dosyaları indirin + alert action + Downloaded + İndirildi No comment provided by engineer. Downloaded files + Dosyalar İndirildi No comment provided by engineer. @@ -2409,6 +2847,11 @@ Bu senin kendi tek kullanımlık bağlantın! Süre No comment provided by engineer. + + E2E encrypted notifications. + Uçtan uca şifrelenmiş bildirimler. + No comment provided by engineer. + Edit Düzenle @@ -2429,6 +2872,10 @@ Bu senin kendi tek kullanımlık bağlantın! Etkinleştir (geçersiz kılmaları koru) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock SimpleX Kilidini etkinleştir @@ -2442,7 +2889,7 @@ Bu senin kendi tek kullanımlık bağlantın! Enable automatic message deletion? Otomatik mesaj silme etkinleştirilsin mi? - No comment provided by engineer. + alert title Enable camera access @@ -2491,6 +2938,7 @@ Bu senin kendi tek kullanımlık bağlantın! Enabled + Etkin No comment provided by engineer. @@ -2568,6 +3016,10 @@ Bu senin kendi tek kullanımlık bağlantın! Şifreleme yeniden anlaşma başarısız oldu. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Şifre gir @@ -2633,26 +3085,36 @@ Bu senin kendi tek kullanımlık bağlantın! Adres değişikliği iptal edilirken hata oluştu No comment provided by engineer. + + Error accepting conditions + Koşulları kabul ederken hata oluştu + alert title + Error accepting contact request Bağlantı isteği kabul edilirken hata oluştu No comment provided by engineer. - - Error accessing database file - Veritabanı dosyasına erişilirken hata oluştu - No comment provided by engineer. - Error adding member(s) Üye(ler) eklenirken hata oluştu No comment provided by engineer. + + Error adding server + Sunucu eklenirken hata oluştu + alert title + Error changing address Adres değiştirilirken hata oluştu No comment provided by engineer. + + Error changing connection profile + Bağlantı profili değiştirilirken hata oluştu + No comment provided by engineer. + Error changing role Rol değiştirilirken hata oluştu @@ -2663,8 +3125,18 @@ Bu senin kendi tek kullanımlık bağlantın! Ayar değiştirilirken hata oluştu No comment provided by engineer. + + Error changing to incognito! + Gizli moduna geçerken hata oluştu! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. + Yönlendirme sunucusu %@'ya bağlanırken hata oluştu. Lütfen daha sonra deneyin. No comment provided by engineer. @@ -2682,6 +3154,10 @@ Bu senin kendi tek kullanımlık bağlantın! Grup bağlantısı oluşturulurken hata oluştu No comment provided by engineer. + + Error creating list + alert title + Error creating member contact Kişi iletişimi oluşturulurken hata oluştu @@ -2697,6 +3173,10 @@ Bu senin kendi tek kullanımlık bağlantın! Profil oluşturulurken hata oluştu! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file Dosya şifresi çözülürken hata oluştu @@ -2764,6 +3244,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error exporting theme: %@ + Tema dışa aktarılırken hata oluştu: %@ No comment provided by engineer. @@ -2776,9 +3257,14 @@ Bu senin kendi tek kullanımlık bağlantın! Gruba katılırken hata oluştu No comment provided by engineer. - - Error loading %@ servers - %@ sunucuları yüklenirken hata oluştu + + Error loading servers + Sunucular yüklenirken hata oluştu + alert title + + + Error migrating settings + Ayarlar taşınırken hata oluştu No comment provided by engineer. @@ -2789,28 +3275,34 @@ Bu senin kendi tek kullanımlık bağlantın! Error receiving file Dosya alınırken sorun oluştu - No comment provided by engineer. + alert title Error reconnecting server + Hata, sunucuya yeniden bağlanılıyor No comment provided by engineer. Error reconnecting servers + Hata sunuculara yeniden bağlanılıyor No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member Kişiyi silerken sorun oluştu No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics - No comment provided by engineer. - - - Error saving %@ servers - %@ sunucuları kaydedilirken sorun oluştu + Hata istatistikler sıfırlanıyor No comment provided by engineer. @@ -2818,6 +3310,10 @@ Bu senin kendi tek kullanımlık bağlantın! ICE sunucularını kaydedirken sorun oluştu No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile Grup profili kaydedilirken sorun oluştu @@ -2833,6 +3329,11 @@ Bu senin kendi tek kullanımlık bağlantın! Parolayı Anahtar Zincirine kaydederken hata oluştu No comment provided by engineer. + + Error saving servers + Sunucular kaydedilirken hata oluştu + alert title + Error saving settings Ayarlar kaydedilirken hata oluştu @@ -2878,16 +3379,25 @@ Bu senin kendi tek kullanımlık bağlantın! Sohbet durdurulurken hata oluştu No comment provided by engineer. + + Error switching profile + Profil değiştirme sırasında hata oluştu + No comment provided by engineer. + Error switching profile! Profil değiştirilirken hata oluştu! - No comment provided by engineer. + alertTitle Error synchronizing connection Bağlantı senkronizasyonunda hata oluştu No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link Grup bağlantısı güncellenirken hata oluştu @@ -2898,6 +3408,11 @@ Bu senin kendi tek kullanımlık bağlantın! Mesaj güncellenirken hata oluştu No comment provided by engineer. + + Error updating server + Sunucu güncellenirken hata oluştu + alert title + Error updating settings Ayarları güncellerken hata oluştu @@ -2926,8 +3441,9 @@ Bu senin kendi tek kullanımlık bağlantın! Error: %@ Hata: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2941,8 +3457,14 @@ Bu senin kendi tek kullanımlık bağlantın! Errors + Hatalar No comment provided by engineer. + + Errors in servers configuration. + Sunucular yapılandırılırken hatalar oluştu. + servers error + Even when disabled in the conversation. Konuşma sırasında devre dışı bırakılsa bile. @@ -2958,6 +3480,10 @@ Bu senin kendi tek kullanımlık bağlantın! Genişlet chat item action + + Expired + token status text + Export database Veritabanını dışarı aktar @@ -2970,6 +3496,7 @@ Bu senin kendi tek kullanımlık bağlantın! Export theme + Temayı dışa aktar No comment provided by engineer. @@ -2997,34 +3524,63 @@ Bu senin kendi tek kullanımlık bağlantın! Hızlı ve gönderici çevrimiçi olana kadar beklemek yok! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. Daha hızlı katılma ve daha güvenilir mesajlar. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite Favori swipe action + + Favorites + No comment provided by engineer. + File error - No comment provided by engineer. + Dosya hatası + file error alert title + + + File errors: +%@ + Dosya hataları: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. + Dosya bulunamadı - muhtemelen dosya silindi veya göderim iptal edildi. file error text File server error: %@ + Dosya sunucusu hatası: %@ file error text File status + Dosya durumu No comment provided by engineer. File status: %@ + Dosya durumu: %@ copied message info @@ -3062,8 +3618,8 @@ Bu senin kendi tek kullanımlık bağlantın! Dosyalar ve medya chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Dosyalar ve medya bu grupta yasaklandı. No comment provided by engineer. @@ -3132,21 +3688,69 @@ Bu senin kendi tek kullanımlık bağlantın! Düzeltme grup üyesi tarafından desteklenmiyor No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + Sohbet profili için %@: + servers error + For console Konsol için No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Örneğin, eğer kişiniz SimpleX Sohbet sunucusundan mesajları alıyorsa, uygulamanız bu mesajları Flux sunucusundan iletecektir. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + Gizli yönlendirme için + No comment provided by engineer. + + + For social media + Sosyal medya için + No comment provided by engineer. + Forward İlet chat item action + + Forward %d message(s)? + %d mesaj(lar)ı iletilsin mi? + alert title + Forward and save messages Mesajları ilet ve kaydet No comment provided by engineer. + + Forward messages + İletileri ilet + alert action + + + Forward messages without files? + Mesajlar dosyalar olmadan iletilsin mi ? + alert message + + + Forward up to 20 messages at once. + Aynı anda en fazla 20 mesaj iletin. + No comment provided by engineer. + Forwarded İletildi @@ -3157,16 +3761,24 @@ Bu senin kendi tek kullanımlık bağlantın! Şuradan iletildi No comment provided by engineer. + + Forwarding %lld messages + %lld mesajlarını ilet + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. + Yönlendirme sunucusu %@, hedef sunucu %@'ya bağlanamadı. Lütfen daha sonra deneyin. No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + Yönlendirme sunucusu adresi ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + Yönlendirme sunucusu sürümü ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. @@ -3203,11 +3815,6 @@ Hata: %2$@ Bütün isim (opsiyonel) No comment provided by engineer. - - Full name: - Bütün isim: - No comment provided by engineer. - Fully decentralized – visible only to members. Tamamiyle merkezi olmayan - sadece kişilere görünür. @@ -3228,12 +3835,18 @@ Hata: %2$@ GİFler ve çıkartmalar No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! + İyi öğlenler! message preview Good morning! + Günaydın! message preview @@ -3291,41 +3904,6 @@ Hata: %2$@ Grup bağlantıları No comment provided by engineer. - - Group members can add message reactions. - Grup üyeleri mesaj tepkileri ekleyebilir. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) - No comment provided by engineer. - - - Group members can send SimpleX links. - Grup üyeleri SimpleX bağlantıları gönderebilir. - No comment provided by engineer. - - - Group members can send direct messages. - Grup üyeleri doğrudan mesajlar gönderebilir. - No comment provided by engineer. - - - Group members can send disappearing messages. - Grup üyeleri kaybolan mesajlar gönderebilir. - No comment provided by engineer. - - - Group members can send files and media. - Grup üyeleri dosyalar ve medya gönderebilir. - No comment provided by engineer. - - - Group members can send voice messages. - Grup üyeleri sesli mesajlar gönderebilir. - No comment provided by engineer. - Group message: Grup mesajı: @@ -3366,11 +3944,19 @@ Hata: %2$@ Grup senden silinecektir - bu geri alınamaz! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Yardım No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Gizlenmiş @@ -3421,10 +4007,19 @@ Hata: %2$@ SimpleX nasıl çalışır No comment provided by engineer. + + How it affects privacy + Gizliliğinizi nasıl etkiler + No comment provided by engineer. + + + How it helps privacy + Gizliliğinizi nasıl arttırır + No comment provided by engineer. + How it works - Nasıl çalışıyor - No comment provided by engineer. + alert button How to @@ -3451,6 +4046,11 @@ Hata: %2$@ ICE sunucuları (her satıra bir tane) No comment provided by engineer. + + IP address + IP adresi + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Eğer onunla buluşamıyorsan görüntülü aramada QR kod göster veya bağlantığı paylaş. @@ -3491,8 +4091,8 @@ Hata: %2$@ Hemen No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Spam ve kötüye kullanıma karşı bağışıklı No comment provided by engineer. @@ -3518,6 +4118,7 @@ Hata: %2$@ Import theme + Temayı içe aktar No comment provided by engineer. @@ -3525,6 +4126,13 @@ Hata: %2$@ Arşiv içe aktarılıyor No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + İyileştirilmiş teslimat, azaltılmış trafik kullanımı. +Daha fazla iyileştirme yakında geliyor! + No comment provided by engineer. + Improved message delivery İyileştirilmiş mesaj iletimi @@ -3555,6 +4163,14 @@ Hata: %2$@ Arama içi sesler No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Gizli @@ -3625,6 +4241,11 @@ Hata: %2$@ [Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Anında + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3632,11 +4253,6 @@ Hata: %2$@ No comment provided by engineer. - - Instantly - Anında - No comment provided by engineer. - Interface Arayüz @@ -3644,8 +4260,29 @@ Hata: %2$@ Interface colors + Arayüz renkleri No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code Geçersiz QR kodu @@ -3684,7 +4321,7 @@ Hata: %2$@ Invalid server address! Geçersiz sunucu adresi! - No comment provided by engineer. + alert title Invalid status @@ -3706,6 +4343,11 @@ Hata: %2$@ Üyeleri davet et No comment provided by engineer. + + Invite to chat + Sohbete davet et + No comment provided by engineer. + Invite to group Gruba davet et @@ -3721,8 +4363,8 @@ Hata: %2$@ Bu sohbette geri döndürülemez mesaj silme yasaktır. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Bu grupta geri döndürülemez mesaj silme yasaktır. No comment provided by engineer. @@ -3749,6 +4391,7 @@ Hata: %2$@ It protects your IP address and connections. + IP adresinizi ve bağlantılarınızı korur. No comment provided by engineer. @@ -3811,10 +4454,11 @@ Bu senin grup için bağlantın %@! Keep Tut - No comment provided by engineer. + alert action Keep conversation + Sohbeti sakla No comment provided by engineer. @@ -3825,7 +4469,7 @@ Bu senin grup için bağlantın %@! Keep unused invitation? Kullanılmamış davet tutulsun mu? - No comment provided by engineer. + alert title Keep your connections @@ -3862,6 +4506,16 @@ Bu senin grup için bağlantın %@! Ayrıl swipe action + + Leave chat + Sohbetten ayrıl + No comment provided by engineer. + + + Leave chat? + Sohbetten ayrılsın mı? + No comment provided by engineer. + Leave group Gruptan ayrıl @@ -3902,6 +4556,18 @@ Bu senin grup için bağlantın %@! Bağlanmış bilgisayarlar No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Canlı mesaj! @@ -3912,11 +4578,6 @@ Bu senin grup için bağlantın %@! Canlı mesajlar No comment provided by engineer. - - Local - Yerel - No comment provided by engineer. - Local name Yerel isim @@ -3937,11 +4598,6 @@ Bu senin grup için bağlantın %@! Kilit modu No comment provided by engineer. - - Make a private connection - Gizli bir bağlantı oluştur - No comment provided by engineer. - Make one message disappear Bir mesajın kaybolmasını sağlayın @@ -3952,21 +4608,11 @@ Bu senin grup için bağlantın %@! Profili gizli yap! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - %@ sunucu adreslerinin doğru formatta olduğundan, satır ayrımı yapıldığından ve yinelenmediğinden (%@) emin olun. - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. WebRTC ICE sunucu adreslerinin doğru formatta olduğundan, satırlara ayrıldığından ve yinelenmediğinden emin olun. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Çoğu kişi sordu: *eğer SimpleX'in hiç kullanıcı tanımlayıcıları yok, o zaman mesajları nasıl gönderebiliyor?* - No comment provided by engineer. - Mark deleted for everyone Herkes için silinmiş olarak işaretle @@ -3994,10 +4640,12 @@ Bu senin grup için bağlantın %@! Media & file servers + Medya ve dosya sunucuları No comment provided by engineer. Medium + Orta blur media @@ -4007,8 +4655,18 @@ Bu senin grup için bağlantın %@! Member inactive + Üye inaktif item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Üye rolü "%@" olarak değiştirilecektir. Tüm sohbet üyeleri bilgilendirilecektir. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Üye rolü "%@" olarak değiştirilecektir. Ve tüm grup üyeleri bilgilendirilecektir. @@ -4019,13 +4677,61 @@ Bu senin grup için bağlantın %@! Üye rolü "%@" olarak değiştirilecektir. Ve üye yeni bir davetiye alacaktır. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Üye gruptan çıkarılacaktır - bu geri alınamaz! No comment provided by engineer. + + Members can add message reactions. + Grup üyeleri mesaj tepkileri ekleyebilir. + 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) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Grup üyeleri SimpleX bağlantıları gönderebilir. + No comment provided by engineer. + + + Members can send direct messages. + Grup üyeleri doğrudan mesajlar gönderebilir. + No comment provided by engineer. + + + Members can send disappearing messages. + Grup üyeleri kaybolan mesajlar gönderebilir. + No comment provided by engineer. + + + Members can send files and media. + Grup üyeleri dosyalar ve medya gönderebilir. + No comment provided by engineer. + + + Members can send voice messages. + Grup üyeleri sesli mesajlar gönderebilir. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus + Menüler No comment provided by engineer. @@ -4050,10 +4756,12 @@ Bu senin grup için bağlantın %@! Message forwarded + Mesaj iletildi item status text Message may be delivered later if member becomes active. + Kullanıcı aktif olursa mesaj iletilebilir. item status description @@ -4071,17 +4779,24 @@ Bu senin grup için bağlantın %@! Mesaj tepkileri bu sohbette yasaklandı. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Mesaj tepkileri bu grupta yasaklandı. No comment provided by engineer. Message reception + Mesaj alındısı No comment provided by engineer. Message servers + Mesaj sunucuları + No comment provided by engineer. + + + Message shape + Mesaj şekli No comment provided by engineer. @@ -4091,10 +4806,12 @@ Bu senin grup için bağlantın %@! Message status + Mesaj durumu No comment provided by engineer. Message status: %@ + Mesaj durumu: %@ copied message info @@ -4122,14 +4839,25 @@ Bu senin grup için bağlantın %@! %@ den gelen mesajlar gösterilecektir! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received + Mesajlar alındı No comment provided by engineer. Messages sent + Mesajlar gönderildi No comment provided by engineer. + + Messages were deleted after you selected them. + Mesajlar siz seçtikten sonra silindi. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Mesajlar, dosyalar ve aramalar **uçtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur. @@ -4195,9 +4923,9 @@ Bu senin grup için bağlantın %@! Geçiş tamamlandı No comment provided by engineer. - - Migrations: %@ - Geçişler: %@ + + Migrations: + Geçişler: No comment provided by engineer. @@ -4215,6 +4943,10 @@ Bu senin grup için bağlantın %@! %@ de yönetildi copied message info + + More + swipe action + More improvements are coming soon! Daha fazla geliştirmeler yakında geliyor! @@ -4225,6 +4957,10 @@ Bu senin grup için bağlantın %@! Daha güvenilir ağ bağlantısı. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Büyük ihtimalle bu bağlantı silinmiş. @@ -4238,7 +4974,11 @@ Bu senin grup için bağlantın %@! Mute Sustur - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4260,6 +5000,10 @@ Bu senin grup için bağlantın %@! Ağ bağlantısı No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu. @@ -4270,6 +5014,10 @@ Bu senin grup için bağlantın %@! Ağ yönetimi No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Ağ ayarları @@ -4280,11 +5028,25 @@ Bu senin grup için bağlantın %@! Ağ durumu No comment provided by engineer. + + New + token status text + New Passcode Yeni şifre No comment provided by engineer. + + New SOCKS credentials will be used every time you start the app. + Uygulamayı her başlattığınızda yeni SOCKS kimlik bilgileri kullanılacaktır. + No comment provided by engineer. + + + New SOCKS credentials will be used for each server. + Her sunucu için yeni SOCKS kimlik bilgileri kullanılacaktır. + No comment provided by engineer. + New chat Yeni sohbet @@ -4292,6 +5054,7 @@ Bu senin grup için bağlantın %@! New chat experience 🎉 + Yeni bir sohbet deneyimi 🎉 No comment provided by engineer. @@ -4304,11 +5067,6 @@ Bu senin grup için bağlantın %@! Yeni kişi: notification - - New database archive - Yeni veritabanı arşivi - No comment provided by engineer. - New desktop app! Yeni bilgisayar uygulaması! @@ -4319,6 +5077,10 @@ Bu senin grup için bağlantın %@! Yeni görünen ad No comment provided by engineer. + + New events + notification + New in %@ %@ da yeni @@ -4326,6 +5088,7 @@ Bu senin grup için bağlantın %@! New media options + Yeni medya seçenekleri No comment provided by engineer. @@ -4343,6 +5106,10 @@ Bu senin grup için bağlantın %@! Yeni parola… No comment provided by engineer. + + New server + No comment provided by engineer. + No Hayır @@ -4353,6 +5120,18 @@ Bu senin grup için bağlantın %@! Uygulama şifresi yok Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Hiçbir kişi seçilmedi @@ -4375,6 +5154,7 @@ Bu senin grup için bağlantın %@! No direct connection yet, message is forwarded by admin. + Henüz direkt bağlantı yok mesaj admin tarafından yönlendirildi. item status description @@ -4394,32 +5174,99 @@ Bu senin grup için bağlantın %@! No info, try to reload + Bilgi yok, yenilemeyi deneyin No comment provided by engineer. + + No media & file servers. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + servers error + No network connection Ağ bağlantısı yok No comment provided by engineer. + + No permission to record speech + Konuşma kaydetme izni yok + No comment provided by engineer. + + + No permission to record video + Video kaydı için izin yok + No comment provided by engineer. + No permission to record voice message Sesli mesaj kaydetmek için izin yok No comment provided by engineer. + + No push server + Yerel + No comment provided by engineer. + No received or sent files Hiç alınmış veya gönderilmiş dosya yok No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No token! + alert title + + + No unread chats + 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! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected + Hiçbir şey seçilmedi No comment provided by engineer. + + Nothing to forward! + Yönlendirilecek bir şey yok! + alert title + Notifications Bildirimler @@ -4430,6 +5277,18 @@ Bu senin grup için bağlantın %@! Bildirimler devre dışı! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4452,18 +5311,13 @@ Bu senin grup için bağlantın %@! Ok Tamam - No comment provided by engineer. + alert button Old database Eski veritabanı No comment provided by engineer. - - Old database archive - Eski veritabanı arşivi - No comment provided by engineer. - One-time invitation link Tek zamanlı bağlantı daveti @@ -4488,13 +5342,18 @@ VPN'nin etkinleştirilmesi gerekir. Onion ana bilgisayarları kullanılmayacaktır. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar. No comment provided by engineer. Only delete conversation + Sadece sohbeti sil No comment provided by engineer. @@ -4512,6 +5371,14 @@ VPN'nin etkinleştirilmesi gerekir. Yalnızca grup sahipleri sesli mesajları etkinleştirebilir. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Sadece siz mesaj tepkileri ekleyebilirsiniz. @@ -4565,13 +5432,17 @@ VPN'nin etkinleştirilmesi gerekir. Open - No comment provided by engineer. + alert action Open Settings Ayarları aç No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Sohbeti aç @@ -4582,35 +5453,41 @@ VPN'nin etkinleştirilmesi gerekir. Sohbet konsolunu aç authentication reason + + Open conditions + No comment provided by engineer. + Open group Grubu aç No comment provided by engineer. + + Open link? + alert title + Open migration to another device Başka bir cihaza açık geçiş authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Kullanıcı profillerini aç - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir. - No comment provided by engineer. - Opening app… Uygulama açılıyor… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + + + Or import archive file + No comment provided by engineer. + Or paste archive link Veya arşiv bağlantısını yapıştırın @@ -4631,14 +5508,25 @@ VPN'nin etkinleştirilmesi gerekir. Veya bu kodu göster No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other Diğer No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + Diğer dosya hataları: +%@ + alert message PING count @@ -4675,6 +5563,11 @@ VPN'nin etkinleştirilmesi gerekir. Şifre ayarlandı! No comment provided by engineer. + + Password + Şifre + No comment provided by engineer. + Password to show Gösterilecek şifre @@ -4707,15 +5600,11 @@ VPN'nin etkinleştirilmesi gerekir. Pending + Bekleniyor No comment provided by engineer. - - People can connect to you only via the links you share. - İnsanlar size yalnızca paylaştığınız bağlantılar üzerinden ulaşabilir. - No comment provided by engineer. - - - Periodically + + Periodic Periyodik olarak No comment provided by engineer. @@ -4731,10 +5620,12 @@ VPN'nin etkinleştirilmesi gerekir. Play from the chat list. + Sohbet listesinden oynat. No comment provided by engineer. Please ask your contact to enable calls. + Lütfen kişinizden çağrılara izin vermesini isteyin. No comment provided by engineer. @@ -4745,6 +5636,8 @@ VPN'nin etkinleştirilmesi gerekir. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + Lütfen telefonun ve bilgisayarın aynı lokal ağa bağlı olduğundan ve bilgisayar güvenlik duvarının bağlantıya izin verdiğinden emin olun. +Lütfen diğer herhangi bir sorunu geliştiricilerle paylaşın. No comment provided by engineer. @@ -4814,11 +5707,28 @@ Hata: %@ Lütfen parolayı güvenli bir şekilde saklayın, kaybederseniz parolayı DEĞİŞTİREMEZSİNİZ. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Lehçe arayüz No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil @@ -4829,16 +5739,15 @@ Hata: %@ Son mesaj taslağını ekleriyle birlikte koru. No comment provided by engineer. - - Preset server - Ön ayarlı sunucu - No comment provided by engineer. - Preset server address Ön ayarlı sunucu adresi No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Ön izleme @@ -4846,6 +5755,7 @@ Hata: %@ Previously connected servers + Önceden bağlanılmış sunucular No comment provided by engineer. @@ -4853,16 +5763,32 @@ Hata: %@ Gizlilik & güvenlik No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + + + Privacy policy and conditions of use. + 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. + No comment provided by engineer. + Private filenames Gizli dosya adları No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing Gizli mesaj yönlendirme @@ -4885,6 +5811,7 @@ Hata: %@ Private routing error + Gizli yönlendirme hatası No comment provided by engineer. @@ -4902,16 +5829,6 @@ Hata: %@ Profil resimleri No comment provided by engineer. - - Profile name - Profil ismi - No comment provided by engineer. - - - Profile name: - Profil ismi: - No comment provided by engineer. - Profile password Profil parolası @@ -4919,12 +5836,13 @@ Hata: %@ Profile theme + Profil teması No comment provided by engineer. Profile update will be sent to your contacts. Profil güncellemesi kişilerinize gönderilecektir. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4946,6 +5864,10 @@ Hata: %@ Mesajlarda tepkileri yasakla. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. SimpleX bağlantısı gönderimini yasakla. @@ -5005,10 +5927,17 @@ Enable in *Network & servers* settings. Proxied + Proxyli No comment provided by engineer. Proxied servers + Proxy sunucuları + No comment provided by engineer. + + + Proxy requires password + Proxy şifre gerektirir No comment provided by engineer. @@ -5033,6 +5962,7 @@ Enable in *Network & servers* settings. Reachable chat toolbar + Erişilebilir sohbet araç çubuğu No comment provided by engineer. @@ -5050,26 +5980,21 @@ Enable in *Network & servers* settings. Dahasını oku No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Daha fazlasını GitHub depomuzdan oku. - 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). @@ -5077,11 +6002,12 @@ Enable in *Network & servers* settings. Receipts are disabled - Gönderildi bilgisi devre dışı bırakıldı + Alıcılar devre dışı bırakıldı No comment provided by engineer. Receive errors + Alım sırasında hata No comment provided by engineer. @@ -5106,14 +6032,17 @@ Enable in *Network & servers* settings. Received messages + Alınan mesajlar No comment provided by engineer. Received reply + Alınan cevap No comment provided by engineer. Received total + Toplam alınan No comment provided by engineer. @@ -5148,6 +6077,7 @@ Enable in *Network & servers* settings. Reconnect + Yeniden bağlan No comment provided by engineer. @@ -5157,18 +6087,22 @@ Enable in *Network & servers* settings. Reconnect all servers + Tüm sunuculara yeniden bağlan No comment provided by engineer. Reconnect all servers? + Tüm sunuculara yeniden bağlansın mı? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + Mesajı göndermeye zorlamak için sunucuya yeniden bağlan. Bu ekstra internet kullanır. No comment provided by engineer. Reconnect server? + Sunucuya yeniden bağlansın mı ? No comment provided by engineer. @@ -5191,11 +6125,23 @@ Enable in *Network & servers* settings. Azaltılmış pil kullanımı No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Reddet reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5222,8 +6168,14 @@ Enable in *Network & servers* settings. Sil No comment provided by engineer. + + Remove archive? + Arşiv kaldırılsın mı ? + No comment provided by engineer. + Remove image + Resmi kaldır No comment provided by engineer. @@ -5286,6 +6238,46 @@ Enable in *Network & servers* settings. Yanıtla chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Gerekli @@ -5298,14 +6290,17 @@ Enable in *Network & servers* settings. Reset all hints + Tüm ip uçlarını sıfırla No comment provided by engineer. Reset all statistics + Tüm istatistikleri sıfırla No comment provided by engineer. Reset all statistics? + Tüm istatistikler sıfırlansın mı ? No comment provided by engineer. @@ -5315,6 +6310,7 @@ Enable in *Network & servers* settings. Reset to app theme + Uygulama temasına sıfırla No comment provided by engineer. @@ -5324,6 +6320,7 @@ Enable in *Network & servers* settings. Reset to user theme + Kullanıcı temasına sıfırla No comment provided by engineer. @@ -5366,6 +6363,10 @@ Enable in *Network & servers* settings. Göster chat item action + + Review conditions + No comment provided by engineer. + Revoke İptal et @@ -5393,6 +6394,12 @@ Enable in *Network & servers* settings. SMP server + SMP sunucusu + No comment provided by engineer. + + + SOCKS proxy + SOCKS vekili No comment provided by engineer. @@ -5408,17 +6415,18 @@ Enable in *Network & servers* settings. Save Kaydet - chat item action + alert button +chat item action Save (and notify contacts) Kaydet (ve kişilere bildir) - No comment provided by engineer. + alert button Save and notify contact Kaydet ve kişilere bildir - No comment provided by engineer. + alert button Save and notify group members @@ -5427,6 +6435,7 @@ Enable in *Network & servers* settings. Save and reconnect + Kayıt et ve yeniden bağlan No comment provided by engineer. @@ -5434,21 +6443,15 @@ Enable in *Network & servers* settings. Kaydet ve grup profilini güncelle No comment provided by engineer. - - Save archive - Arşivi kaydet - No comment provided by engineer. - - - Save auto-accept settings - Otomatik kabul et ayarlarını kaydet - No comment provided by engineer. - Save group profile Grup profilini kaydet No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Parolayı kaydet ve sohbeti aç @@ -5462,7 +6465,7 @@ Enable in *Network & servers* settings. Save preferences? Tercihler kaydedilsin mi? - No comment provided by engineer. + alert title Save profile password @@ -5477,18 +6480,18 @@ Enable in *Network & servers* settings. Save servers? Sunucular kaydedilsin mi? - No comment provided by engineer. - - - Save settings? - Ayarlar kaydedilsin mi? - No comment provided by engineer. + alert title Save welcome message? Hoşgeldin mesajı kaydedilsin mi? No comment provided by engineer. + + Save your profile? + Profiliniz kaydedilsin mi? + alert title + Saved Kaydedildi @@ -5509,12 +6512,19 @@ Enable in *Network & servers* settings. Kaydedilmiş mesaj message info title + + Saving %lld messages + %lld mesajlarını kaydet + No comment provided by engineer. + Scale + Ölçeklendir No comment provided by engineer. Scan / Paste link + Tara / Bağlantı yapıştır No comment provided by engineer. @@ -5559,6 +6569,7 @@ Enable in *Network & servers* settings. Secondary + İkincil renk No comment provided by engineer. @@ -5568,6 +6579,7 @@ Enable in *Network & servers* settings. Secured + Güvenli No comment provided by engineer. @@ -5585,12 +6597,19 @@ Enable in *Network & servers* settings. Seç chat item action + + Select chat profile + Sohbet profili seç + No comment provided by engineer. + Selected %lld + Seçilen %lld No comment provided by engineer. Selected chat preferences prohibit this message. + Seçilen sohbet tercihleri bu mesajı yasakladı. No comment provided by engineer. @@ -5640,6 +6659,7 @@ Enable in *Network & servers* settings. Send errors + Gönderme hataları No comment provided by engineer. @@ -5654,6 +6674,7 @@ Enable in *Network & servers* settings. Send message to enable calls. + Çağrıları aktif etmek için mesaj gönder. No comment provided by engineer. @@ -5671,9 +6692,8 @@ Enable in *Network & servers* settings. Bildirimler gönder No comment provided by engineer. - - Send notifications: - Bildirimler gönder: + + Send private reports No comment provided by engineer. @@ -5699,7 +6719,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Gönderici dosya gönderimini iptal etti. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5713,7 +6733,7 @@ Enable in *Network & servers* settings. Sending delivery receipts will be enabled for all contacts. - Gönderildi bilgisi tüm kişiler için etkinleştirilecektir. + Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek. No comment provided by engineer. @@ -5758,6 +6778,7 @@ Enable in *Network & servers* settings. Sent directly + Direkt gönderildi No comment provided by engineer. @@ -5772,6 +6793,7 @@ Enable in *Network & servers* settings. Sent messages + Gönderilen mesajlar No comment provided by engineer. @@ -5781,18 +6803,31 @@ Enable in *Network & servers* settings. Sent reply + Gönderilen cevap No comment provided by engineer. Sent total + Gönderilen tüm mesajların toplamı No comment provided by engineer. Sent via proxy + Bir proxy aracılığıyla gönderildi No comment provided by engineer. + + Server + Sunucu + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address + Sunucu adresi No comment provided by engineer. @@ -5802,8 +6837,21 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. + Sunucu adresi ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin @@ -5821,6 +6869,7 @@ Enable in *Network & servers* settings. Server type + Sunucu tipi No comment provided by engineer. @@ -5830,6 +6879,7 @@ Enable in *Network & servers* settings. Server version is incompatible with your app: %@. + Sunucu sürümü uygulamanızla uyumlu değil: %@. No comment provided by engineer. @@ -5839,10 +6889,12 @@ Enable in *Network & servers* settings. Servers info + Sunucu bilgileri No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + Sunucu istatistikleri sıfırlanacaktır - bu geri alınamaz! No comment provided by engineer. @@ -5855,6 +6907,10 @@ Enable in *Network & servers* settings. 1 günlüğüne ayarla No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Kişi adı gir… @@ -5862,6 +6918,7 @@ Enable in *Network & servers* settings. Set default theme + Varsayılan temaya ayarla No comment provided by engineer. @@ -5874,6 +6931,10 @@ Enable in *Network & servers* settings. Sistem kimlik doğrulaması yerine ayarla. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Şifre ayarla @@ -5904,6 +6965,11 @@ Enable in *Network & servers* settings. Ayarlar No comment provided by engineer. + + Settings were changed. + Ayarlar değiştirildi. + alert message + Shape profile images Profil resimlerini şekillendir @@ -5912,25 +6978,39 @@ Enable in *Network & servers* settings. Share Paylaş - chat item action + alert action +chat item action Share 1-time link Tek kullanımlık bağlantıyı paylaş No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Adresi paylaş No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Kişilerle adres paylaşılsın mı? - No comment provided by engineer. + alert title Share from other apps. + Diğer uygulamalardan paylaşın. No comment provided by engineer. @@ -5938,6 +7018,11 @@ Enable in *Network & servers* settings. Bağlantıyı paylaş No comment provided by engineer. + + Share profile + Profil paylaş + No comment provided by engineer. + Share this 1-time invite link Bu tek kullanımlık bağlantı davetini paylaş @@ -5945,6 +7030,7 @@ Enable in *Network & servers* settings. Share to SimpleX + SimpleX ile paylaş No comment provided by engineer. @@ -5952,6 +7038,10 @@ Enable in *Network & servers* settings. Kişilerle paylaş No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code QR kodunu göster @@ -5979,6 +7069,7 @@ Enable in *Network & servers* settings. Show percentage + Yüzdeyi göster No comment provided by engineer. @@ -5998,6 +7089,7 @@ Enable in *Network & servers* settings. SimpleX + SimpleX No comment provided by engineer. @@ -6005,6 +7097,10 @@ Enable in *Network & servers* settings. SimpleX Adresi No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat güvenliği Trails of Bits tarafından denetlenmiştir. @@ -6035,6 +7131,18 @@ Enable in *Network & servers* settings. SimpleX adresi No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX kişi adresi @@ -6055,8 +7163,8 @@ Enable in *Network & servers* settings. SimpleX bağlantıları chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. SimpleX bağlantıları bu grupta yasaklandı. No comment provided by engineer. @@ -6070,6 +7178,11 @@ Enable in *Network & servers* settings. SimpleX tek kullanımlık davet simplex link type + + SimpleX protocols reviewed by Trail of Bits. + SimpleX protokolleri Trail of Bits tarafından incelenmiştir. + No comment provided by engineer. + Simplified incognito mode Basitleştirilmiş gizli mod @@ -6077,6 +7190,7 @@ Enable in *Network & servers* settings. Size + Boyut No comment provided by engineer. @@ -6096,10 +7210,17 @@ Enable in *Network & servers* settings. Soft + Yumuşak blur media + + Some app settings were not migrated. + Bazı uygulama ayarları taşınamadı. + No comment provided by engineer. + Some file(s) were not exported: + Bazı dosya(lar) dışa aktarılmadı: No comment provided by engineer. @@ -6109,13 +7230,24 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: + İçe aktarma sırasında bazı önemli olmayan hatalar oluştu: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Biri notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. Kare,daire, veya aralarında herhangi bir şey. @@ -6138,10 +7270,12 @@ Enable in *Network & servers* settings. Starting from %@. + %@'dan başlayarak. No comment provided by engineer. Statistics + İstatistikler No comment provided by engineer. @@ -6159,11 +7293,6 @@ Enable in *Network & servers* settings. Sohbeti kes No comment provided by engineer. - - Stop chat to enable database actions - Veritabanı eylemlerini etkinleştirmek için sohbeti durdur - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Sohbet veritabanını dışa aktarmak, içe aktarmak veya silmek için sohbeti durdurun. Sohbet durdurulduğunda mesaj alamaz ve gönderemezsiniz. @@ -6192,20 +7321,25 @@ Enable in *Network & servers* settings. Stop sharing Paylaşmayı durdur - No comment provided by engineer. + alert action Stop sharing address? Adresi paylaşmak durdurulsun mu? - No comment provided by engineer. + alert title Stopping chat Sohbeti durdurma No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong + Güçlü blur media @@ -6215,14 +7349,17 @@ Enable in *Network & servers* settings. Subscribed + Abone olundu No comment provided by engineer. Subscription errors + Abone olurken hata No comment provided by engineer. Subscriptions ignored + Abonelikler göz ardı edildi No comment provided by engineer. @@ -6230,6 +7367,16 @@ Enable in *Network & servers* settings. SimpleX Chat'e destek ol No comment provided by engineer. + + Switch audio and video during the call. + Görüşme sırasında ses ve görüntüyü değiştirin. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Sohbet profilini 1 kerelik davetler için değiştirin. + No comment provided by engineer. + System Sistem @@ -6242,6 +7389,7 @@ Enable in *Network & servers* settings. TCP connection + TCP bağlantısı No comment provided by engineer. @@ -6249,6 +7397,10 @@ Enable in *Network & servers* settings. TCP bağlantı zaman aşımı No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6264,11 +7416,20 @@ Enable in *Network & servers* settings. TCP_TVLDEKAL No comment provided by engineer. + + Tail + Konuşma balonu + No comment provided by engineer. + Take picture Fotoğraf çek No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Tuşa bas @@ -6306,13 +7467,18 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + Geçici dosya hatası + file error alert title Test failed at step %@. Test %@ adımında başarısız oldu. server test failure + + Test notifications + No comment provided by engineer. + Test server Sunucuyu test et @@ -6326,7 +7492,7 @@ Enable in *Network & servers* settings. Tests failed! Testler başarısız oldu! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6343,11 +7509,6 @@ Enable in *Network & servers* settings. Kullanıcılar için teşekkürler - Weblate aracılığıyla katkıda bulun! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Herhangi bir kullanıcı tanımlayıcısı olmayan ilk platform - tasarım gereği gizli. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6360,6 +7521,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Uygulama bilinmeyen dosya sunucularından indirmeleri onaylamanızı isteyecektir (.onion hariç). @@ -6375,6 +7540,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Bağlantı kabulünüz iptal edilecektir! @@ -6395,6 +7564,11 @@ 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 + No comment provided by engineer. + The hash of the previous message is different. Önceki mesajın hash'i farklı. @@ -6412,15 +7586,12 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The messages will be deleted for all members. + Mesajlar tüm üyeler için silinecektir. No comment provided by engineer. The messages will be marked as moderated for all members. - No comment provided by engineer. - - - The next generation of private messaging - Gizli mesajlaşmanın yeni nesli + Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir. No comment provided by engineer. @@ -6428,9 +7599,12 @@ 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 profile is only shared with your contacts. - Profil sadece kişilerinle paylaşılacak. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6448,13 +7622,27 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Mevcut sohbet profilinizin yeni bağlantıları için sunucular **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Yapıştırdığın metin bir SimpleX bağlantısı değildir. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + Yüklenen veritabanı arşivi sunuculardan kalıcı olarak kaldırılacaktır. + No comment provided by engineer. + Themes + Temalar + No comment provided by engineer. + + + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -6477,6 +7665,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Bu işlem geri alınamaz - seçilenden daha önce gönderilen ve alınan mesajlar silinecektir. Bu işlem birkaç dakika sürebilir. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Bu işlem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri döndürülemez şekilde kaybolacaktır. @@ -6522,8 +7714,17 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Bu senin kendi tek kullanımlık bağlantın! 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. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. + Bu bağlantı başka bir mobil cihazda kullanıldı, lütfen masaüstünde yeni bir bağlantı oluşturun. + No comment provided by engineer. + + + This message was deleted or not received yet. No comment provided by engineer. @@ -6533,6 +7734,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Title + Başlık No comment provided by engineer. @@ -6555,9 +7757,8 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Yeni bir bağlantı oluşturmak için No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6577,6 +7778,25 @@ You will be prompted to complete authentication before this feature is enabled.< Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenecektir. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Konuşmayı kaydetmek için lütfen Mikrofon kullanma izni verin. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Video kaydetmek için lütfen Kamera kullanım izni verin. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Sesli mesaj kaydetmek için lütfen Mikrofon kullanım izni verin. @@ -6587,11 +7807,19 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Gizli profilinizi ortaya çıkarmak için **Sohbet profilleriniz** sayfasındaki arama alanına tam bir şifre girin. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Anlık anlık bildirimleri desteklemek için sohbet veritabanının taşınması gerekir. No comment provided by engineer. + + 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. Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın). @@ -6599,6 +7827,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Toggle chat list: + Sohbet listesini değiştir: No comment provided by engineer. @@ -6606,12 +7835,18 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Bağlanırken gizli moda geçiş yap. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity + Araç çubuğu opaklığı No comment provided by engineer. Total + Toplam No comment provided by engineer. @@ -6621,6 +7856,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Transport sessions + Taşıma oturumları No comment provided by engineer. @@ -6678,6 +7914,10 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Üyenin engeli kaldırılsın mı? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Beklenmeyen geçiş durumu @@ -6726,7 +7966,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Unknown servers! Bilinmeyen sunucular! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6763,13 +8003,17 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Unmute Susturmayı kaldır - swipe action + notification label action Unread Okunmamış swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Yeni üyelere 100e kadar en son mesajlar gönderildi. @@ -6792,6 +8036,11 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Update settings? + Ayarları güncelleyelim mi? + No comment provided by engineer. + + + Updated conditions No comment provided by engineer. @@ -6806,6 +8055,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Upload errors + Yükleme hataları No comment provided by engineer. @@ -6820,10 +8070,12 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uploaded + Yüklendi No comment provided by engineer. Uploaded files + Yüklenen dosyalar No comment provided by engineer. @@ -6831,16 +8083,33 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Arşiv yükleme No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts .onion ana bilgisayarlarını kullan No comment provided by engineer. + + Use SOCKS proxy + SOCKS vekili kullan + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX Chat sunucuları kullanılsın mı? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Sohbeti kullan @@ -6851,6 +8120,14 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Şu anki profili kullan No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Yeni bağlantılar için kullan @@ -6891,6 +8168,14 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sunucu kullan No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Görüşme sırasında uygulamayı kullanın. @@ -6898,15 +8183,21 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use the app with one hand. + Uygulamayı tek elle kullan. No comment provided by engineer. - - User profile - Kullanıcı profili + + Use web port No comment provided by engineer. User selection + Kullanıcı seçimi + No comment provided by engineer. + + + Username + Kullanıcı Adı No comment provided by engineer. @@ -6979,11 +8270,19 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste 1gb'a kadar videolar ve dosyalar No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Güvenlik kodunu görüntüle No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Görünür geçmiş @@ -6999,8 +8298,8 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Bu sohbette sesli mesajlar yasaktır. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Bu grupta sesli mesajlar yasaktır. No comment provided by engineer. @@ -7041,10 +8340,12 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Wallpaper accent + Duvar kağıdı vurgusu No comment provided by engineer. Wallpaper background + Duvar kağıdı arkaplanı No comment provided by engineer. @@ -7092,9 +8393,8 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sesli ve görüntülü aramalara bağlanırken. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - İnsanlar bağlantı talebinde bulunduğunda, kabul edebilir veya reddedebilirsiniz. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7140,7 +8440,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından görülebilir: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7154,6 +8454,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Wrong key or unknown file chunk address - most likely file is deleted. + Yanlış anahtar veya bilinmeyen dosya yığın adresi - büyük olasılıkla dosya silinmiştir. file error text @@ -7163,11 +8464,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste XFTP server - No comment provided by engineer. - - - You - Sen + XFTP sunucusu No comment provided by engineer. @@ -7195,6 +8492,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Zaten %@'a bağlısınız. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Zaten %@'a bağlanıyorsunuz. @@ -7244,6 +8545,7 @@ Katılma isteği tekrarlansın mı? You are not connected to these servers. Private routing is used to deliver messages to them. + Bu sunuculara bağlı değilsiniz. Mesajları onlara iletmek için özel yönlendirme kullanılır. No comment provided by engineer. @@ -7253,6 +8555,11 @@ Katılma isteği tekrarlansın mı? You can change it in Appearance settings. + Görünüm ayarlarından değiştirebilirsiniz. + No comment provided by engineer. + + + You can configure servers via settings. No comment provided by engineer. @@ -7292,6 +8599,11 @@ Katılma isteği tekrarlansın mı? You can send messages to %@ from Archived contacts. + Arşivlenen kişilerden %@'ya mesaj gönderebilirsiniz. + No comment provided by engineer. + + + You can set connection name, to remember who the link was shared with. No comment provided by engineer. @@ -7309,11 +8621,6 @@ Katılma isteği tekrarlansın mı? Bu adresi kişilerinizle paylaşarak onların **%@** ile bağlantı kurmasını sağlayabilirsiniz. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Adresinizi bir bağlantı veya QR kodu olarak paylaşabilirsiniz - herkes size bağlanabilir. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Sohbeti uygulamada Ayarlar / Veritabanı üzerinden veya uygulamayı yeniden başlatarak başlatabilirsiniz @@ -7321,6 +8628,7 @@ Katılma isteği tekrarlansın mı? You can still view conversation with %@ in the list of chats. + Sohbet listesinde %@ ile konuşmayı görüntülemeye devam edebilirsiniz. No comment provided by engineer. @@ -7336,23 +8644,23 @@ Katılma isteği tekrarlansın mı? You can view invitation link again in connection details. Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin. - No comment provided by engineer. + alert message You can't send messages! Mesajlar gönderemezsiniz! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Mesajların hangi sunucu(lar)dan **alınacağını**, kişilerinizi - onlara mesaj göndermek için kullandığınız sunucuları - siz kontrol edersiniz. - 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. + No comment provided by engineer. + You have already requested connection via this address! Bu adres üzerinden zaten bağlantı talebinde bulundunuz! @@ -7387,10 +8695,12 @@ Bağlantı isteği tekrarlansın mı? You may migrate the exported database. + Dışa aktarılan veritabanını taşıyabilirsiniz. No comment provided by engineer. You may save the exported archive. + Dışa aktarılan arşivi kaydedebilirsiniz. No comment provided by engineer. @@ -7400,6 +8710,7 @@ Bağlantı isteği tekrarlansın mı? You need to allow your contact to call to be able to call them. + Kendiniz arayabilmeniz için önce irtibat kişinizin sizi aramasına izin vermelisiniz. No comment provided by engineer. @@ -7417,6 +8728,10 @@ Bağlantı isteği tekrarlansın mı? Grup daveti gönderdiniz No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! Grup sahibinin cihazı çevrimiçi olduğunda gruba bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin! @@ -7452,6 +8767,10 @@ Bağlantı isteği tekrarlansın mı? Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Bu gruptan artık mesaj almayacaksınız. Sohbet geçmişi korunacaktır. @@ -7472,31 +8791,16 @@ Bağlantı isteği tekrarlansın mı? Bu grup için gizli bir profil kullanıyorsunuz - ana profilinizi paylaşmayı önlemek için kişileri davet etmeye izin verilmiyor No comment provided by engineer. - - Your %@ servers - %@ sunucularınız - No comment provided by engineer. - Your ICE servers ICE sunucularınız No comment provided by engineer. - - Your SMP servers - SMP sunucularınız - No comment provided by engineer. - Your SimpleX address SimpleX adresin No comment provided by engineer. - - Your XFTP servers - XFTP sunucularınız - No comment provided by engineer. - Your calls Aramaların @@ -7512,11 +8816,21 @@ Bağlantı isteği tekrarlansın mı? Sohbet veritabanınız şifrelenmemiş - şifrelemek için parola ayarlayın. No comment provided by engineer. + + Your chat preferences + Sohbet tercihleriniz + alert title + Your chat profiles Sohbet profillerin No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Bağlantınız %@ adresine taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kişiniz şu anda desteklenen maksimum boyuttan (%@) daha büyük bir dosya gönderdi. @@ -7532,6 +8846,11 @@ Bağlantı isteği tekrarlansın mı? Kişileriniz bağlı kalacaktır. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Kimlik bilgileriniz şifrelenmeden gönderilebilir. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Mevcut sohbet veritabanınız SİLİNECEK ve içe aktarılan veritabanıyla DEĞİŞTİRİLECEKTİR. @@ -7562,33 +8881,35 @@ Bağlantı isteği tekrarlansın mı? Profiliniz **%@** paylaşılacaktır. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. -SimpleX sunucuları profilinizi göremez. + + Your profile is stored on your device and only shared with your contacts. + Profil sadece kişilerinle paylaşılacak. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir. + alert message + Your random profile Rasgele profiliniz No comment provided by engineer. - - Your server - Sunucunuz - No comment provided by engineer. - Your server address Sunucu adresiniz No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Ayarlarınız @@ -7629,6 +8950,10 @@ SimpleX sunucuları profilinizi göremez. kabul edilen arama call status + + accepted invitation + chat list item title + admin yönetici @@ -7664,8 +8989,13 @@ SimpleX sunucuları profilinizi göremez. ve %lld diğer etkinlikler No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts + denemeler No comment provided by engineer. @@ -7701,7 +9031,8 @@ SimpleX sunucuları profilinizi göremez. blocked by admin yönetici tarafından engellendi - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7710,6 +9041,7 @@ SimpleX sunucuları profilinizi göremez. call + Ara No comment provided by engineer. @@ -7815,7 +9147,7 @@ SimpleX sunucuları profilinizi göremez. connecting… bağlanılıyor… - chat list item title + No comment provided by engineer. connection established @@ -7864,12 +9196,14 @@ SimpleX sunucuları profilinizi göremez. decryption errors + Şifre çözme hataları No comment provided by engineer. default (%@) varsayılan (%@) - pref value + delete after time +pref value default (no) @@ -7918,6 +9252,7 @@ SimpleX sunucuları profilinizi göremez. duplicates + Kopyalar No comment provided by engineer. @@ -7995,13 +9330,9 @@ SimpleX sunucuları profilinizi göremez. hata No comment provided by engineer. - - event happened - etkinlik yaşandı - No comment provided by engineer. - expired + Süresi dolmuş No comment provided by engineer. @@ -8036,6 +9367,7 @@ SimpleX sunucuları profilinizi göremez. inactive + inaktif No comment provided by engineer. @@ -8080,6 +9412,7 @@ SimpleX sunucuları profilinizi göremez. invite + davet No comment provided by engineer. @@ -8139,6 +9472,7 @@ SimpleX sunucuları profilinizi göremez. message + mesaj No comment provided by engineer. @@ -8166,19 +9500,19 @@ SimpleX sunucuları profilinizi göremez. %@ tarafından yönetilmekte marked deleted chat item preview text + + moderator + member role + months aylar time unit - - mute - No comment provided by engineer. - never asla - No comment provided by engineer. + delete after time new message @@ -8209,8 +9543,8 @@ SimpleX sunucuları profilinizi göremez. off kapalı enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8229,10 +9563,12 @@ SimpleX sunucuları profilinizi göremez. other + diğer No comment provided by engineer. other errors + diğer hatalar No comment provided by engineer. @@ -8250,6 +9586,14 @@ SimpleX sunucuları profilinizi göremez. eşler arası No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption kuantuma dayanıklı e2e şifreleme @@ -8265,6 +9609,10 @@ SimpleX sunucuları profilinizi göremez. onaylama alındı… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call geri çevrilmiş çağrı @@ -8295,6 +9643,10 @@ SimpleX sunucuları profilinizi göremez. sen kaldırıldın rcv group event chat item + + requested to connect + chat list item title + saved kaydedildi @@ -8307,6 +9659,7 @@ SimpleX sunucuları profilinizi göremez. search + ara No comment provided by engineer. @@ -8393,10 +9746,6 @@ son alınan msj: %2$@ bilinmeyen durum No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected korumasız @@ -8444,6 +9793,7 @@ son alınan msj: %2$@ video + video No comment provided by engineer. @@ -8560,7 +9910,7 @@ son alınan msj: %2$@
- +
@@ -8597,7 +9947,7 @@ son alınan msj: %2$@
- +
@@ -8617,176 +9967,243 @@ son alınan msj: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright © 2024 SimpleX Chat. All rights reserved. + Telif Hakkı © 2024 SimpleX Chat. Tüm hakları saklıdır. Copyright (human-readable)
- +
%@ + %@ No comment provided by engineer. App is locked! + Uygulama kilitlendi! No comment provided by engineer. Cancel + İptal et No comment provided by engineer. Cannot access keychain to save database password + Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor No comment provided by engineer. Cannot forward message + Mesaj iletilemiyor No comment provided by engineer. Comment + Yorum No comment provided by engineer. Currently maximum supported file size is %@. + Şu anki maksimum desteklenen dosya boyutu %@ kadardır. No comment provided by engineer. Database downgrade required + Veritabanı sürüm düşürme gerekli No comment provided by engineer. Database encrypted! + Veritabanı şifrelendi! No comment provided by engineer. Database error + Veritabanı hatası No comment provided by engineer. Database passphrase is different from saved in the keychain. + Veritabanı parolası Anahtar Zinciri'nde kayıtlı olandan farklıdır. No comment provided by engineer. Database passphrase is required to open chat. + Konuşmayı açmak için veri tabanı parolası gerekli. No comment provided by engineer. Database upgrade required + Veritabanı yükseltmesi gerekli No comment provided by engineer. Error preparing file + Dosya hazırlanırken hata oluştu No comment provided by engineer. Error preparing message + Mesaj hazırlanırken hata oluştu No comment provided by engineer. Error: %@ + Hata: %@ No comment provided by engineer. File error + Dosya hatası No comment provided by engineer. Incompatible database version + Uyumsuz veritabanı sürümü No comment provided by engineer. Invalid migration confirmation + Geçerli olmayan taşıma onayı No comment provided by engineer. Keychain error + Anahtarlık hatası No comment provided by engineer. Large file! + Büyük dosya! No comment provided by engineer. No active profile + Aktif profil yok No comment provided by engineer. Ok + Tamam No comment provided by engineer. Open the app to downgrade the database. + Veritabanının sürümünü düşürmek için uygulamayı açın. No comment provided by engineer. Open the app to upgrade the database. + Veritabanını güncellemek için uygulamayı açın. No comment provided by engineer. Passphrase + Parola No comment provided by engineer. Please create a profile in the SimpleX app + Lütfen SimpleX uygulamasında bir profil oluşturun No comment provided by engineer. Selected chat preferences prohibit this message. + Seçilen sohbet tercihleri bu mesajı yasakladı. No comment provided by engineer. Sending a message takes longer than expected. + Mesaj göndermek beklenenden daha uzun sürüyor. No comment provided by engineer. Sending message… + Mesaj gönderiliyor… No comment provided by engineer. Share + Paylaş No comment provided by engineer. Slow network? + Ağ yavaş mı? No comment provided by engineer. Unknown database error: %@ + Bilinmeyen veritabanı hatası: %@ No comment provided by engineer. Unsupported format + Desteklenmeyen format No comment provided by engineer. Wait + Bekleyin No comment provided by engineer. Wrong database passphrase + Yanlış veritabanı parolası No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + Gizlilik ve Güvenlik / SimpleX Lock ayarlarından paylaşıma izin verebilirsiniz. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/contents.json b/apps/ios/SimpleX Localizations/tr.xcloc/contents.json index 6f74640a6b..2e32ea2080 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/tr.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "tr", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 7bcb30c1db..c0375e3b02 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (можна скопіювати) @@ -127,6 +100,16 @@ %@ перевірено No comment provided by engineer. + + %@ server + %@ сервер + No comment provided by engineer. + + + %@ servers + %@ сервери + No comment provided by engineer. + %@ uploaded %@ завантажено @@ -137,6 +120,11 @@ %@ хоче підключитися! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ та %lld учасників @@ -157,11 +145,36 @@ %d днів time interval + + %d file(s) are still being downloaded. + %их файл(ів) ще досі завантажуються. + forward confirmation reason + + + %d file(s) failed to download. + %их файлів не вийшло завантажити. + forward confirmation reason + + + %d file(s) were deleted. + %их файл(ів) було видалено. + forward confirmation reason + + + %d file(s) were not downloaded. + %d файл(и) не було завантажено. + forward confirmation reason + %d hours %d годин time interval + + %d messages not forwarded + %d повідомлень не переслано + alert title + %d min %d хв @@ -177,6 +190,10 @@ %d сек time interval + + %d seconds(s) + delete after time + %d skipped message(s) %d пропущено повідомлення(ь) @@ -247,11 +264,6 @@ %lld нові мови інтерфейсу No comment provided by engineer. - - %lld second(s) - %lld секунд(и) - No comment provided by engineer. - %lld seconds %lld секунд @@ -302,11 +314,6 @@ %u повідомлень пропущено. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (новий) @@ -317,19 +324,9 @@ (цей пристрій v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Додати контакт**: створити нове посилання-запрошення або підключитися за отриманим посиланням. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Додати новий контакт**: щоб створити одноразовий QR-код або посилання для свого контакту. + + **Create 1-time link**: to create and share a new invitation link. + **Додати контакт**: створити нове посилання-запрошення. No comment provided by engineer. @@ -337,13 +334,13 @@ **Створити групу**: створити нову групу. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Більш приватний**: перевіряти нові повідомлення кожні 20 хвилин. Серверу SimpleX Chat передається токен пристрою, але не кількість контактів або повідомлень, які ви маєте. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Найбільш приватний**: не використовуйте сервер сповіщень SimpleX Chat, періодично перевіряйте повідомлення у фоновому режимі (залежить від того, як часто ви користуєтесь додатком). No comment provided by engineer. @@ -357,11 +354,16 @@ **Зверніть увагу: ви НЕ зможете відновити або змінити пароль, якщо втратите його. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Рекомендується**: токен пристрою та сповіщення надсилаються на сервер сповіщень SimpleX Chat, але не вміст повідомлення, його розмір або від кого воно надійшло. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + **Відсканувати / Вставити посилання**: підключитися за отриманим посиланням. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Попередження**: Для отримання миттєвих пуш-сповіщень потрібна парольна фраза, збережена у брелоку. @@ -387,11 +389,6 @@ \*жирний* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -428,11 +425,6 @@ - історія редагування. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 сек @@ -446,7 +438,8 @@ 1 day 1 день - time interval + delete after time +time interval 1 hour @@ -461,12 +454,28 @@ 1 month 1 місяць - time interval + delete after time +time interval 1 week 1 тиждень - time interval + delete after time +time interval + + + 1 year + delete after time + + + 1-time link + Одноразове посилання + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + Одноразове посилання можна використовувати *тільки з одним контактом* - поділіться ним особисто або через будь-який месенджер. + No comment provided by engineer. 5 minutes @@ -483,11 +492,6 @@ 30 секунд No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -537,19 +541,14 @@ Скасувати зміну адреси? No comment provided by engineer. - - About SimpleX - Про SimpleX - No comment provided by engineer. - About SimpleX Chat Про чат SimpleX No comment provided by engineer. - - About SimpleX address - Про адресу SimpleX + + About operators + Про операторів No comment provided by engineer. @@ -561,8 +560,13 @@ Accept Прийняти accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + Прийняти умови + No comment provided by engineer. Accept connection request? @@ -578,7 +582,12 @@ Accept incognito Прийняти інкогніто accept contact request via notification - swipe action +swipe action + + + Accepted conditions + Прийняті умови + No comment provided by engineer. Acknowledged @@ -590,6 +599,10 @@ Помилки підтвердження No comment provided by engineer. + + Active + token status text + Active connections Активні з'єднання @@ -600,14 +613,13 @@ Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. No comment provided by engineer. - - Add contact - Додати контакт + + Add friends + Додайте друзів No comment provided by engineer. - - Add preset servers - Додавання попередньо встановлених серверів + + Add list No comment provided by engineer. @@ -625,16 +637,40 @@ Додайте сервери, відсканувавши QR-код. No comment provided by engineer. + + Add team members + Додайте учасників команди + No comment provided by engineer. + Add to another device Додати до іншого пристрою No comment provided by engineer. + + Add to list + No comment provided by engineer. + Add welcome message Додати вітальне повідомлення No comment provided by engineer. + + Add your team members to the conversations. + Додайте членів своєї команди до розмов. + No comment provided by engineer. + + + Added media & file servers + Додано медіа та файлові сервери + No comment provided by engineer. + + + Added message servers + Додано сервери повідомлень + No comment provided by engineer. + Additional accent Додатковий акцент @@ -660,6 +696,16 @@ Зміна адреси буде скасована. Буде використано стару адресу отримання. No comment provided by engineer. + + Address or 1-time link? + Адреса чи одноразове посилання? + No comment provided by engineer. + + + Address settings + Налаштування адреси + No comment provided by engineer. + Admins can block a member for all. Адміністратори можуть заблокувати користувача для всіх. @@ -680,6 +726,10 @@ Додаткові налаштування No comment provided by engineer. + + All + No comment provided by engineer. + All app data is deleted. Всі дані програми видаляються. @@ -690,13 +740,17 @@ Всі чати та повідомлення будуть видалені - це неможливо скасувати! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + alert message + All data is erased when it is entered. Всі дані стираються при введенні. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Всі дані є приватними для вашого пристрою. No comment provided by engineer. @@ -705,6 +759,11 @@ Всі учасники групи залишаться на зв'язку. 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! Усі повідомлення будуть видалені - цю дію не можна скасувати! @@ -723,6 +782,14 @@ All profiles Всі профілі + profile dropdown + + + All reports will be archived for you. + No comment provided by engineer. + + + All servers No comment provided by engineer. @@ -800,6 +867,10 @@ Дозволяє безповоротно видаляти надіслані повідомлення. (24 години) No comment provided by engineer. + + Allow to report messsages to moderators. + No comment provided by engineer. + Allow to send SimpleX links. Дозволити надсилати посилання SimpleX. @@ -880,11 +951,20 @@ Створюється порожній профіль чату з вказаним ім'ям, і додаток відкривається у звичайному режимі. No comment provided by engineer. + + Another reason + report reason + Answer call Відповісти на дзвінок No comment provided by engineer. + + Anybody can host servers. + Кожен може хостити сервери. + No comment provided by engineer. + App build: %@ Збірка програми: %@ @@ -900,6 +980,10 @@ Додаток шифрує нові локальні файли (крім відео). No comment provided by engineer. + + App group: + No comment provided by engineer. + App icon Іконка програми @@ -915,6 +999,11 @@ Пароль програми замінено на пароль самознищення. No comment provided by engineer. + + App session + Сесія програми + No comment provided by engineer. + App version Версія програми @@ -940,6 +1029,18 @@ Звертатися до No comment provided by engineer. + + Archive + No comment provided by engineer. + + + Archive %lld reports? + No comment provided by engineer. + + + Archive all reports? + No comment provided by engineer. + Archive and upload Архівування та завантаження @@ -950,6 +1051,18 @@ Архівуйте контакти, щоб поспілкуватися пізніше. No comment provided by engineer. + + Archive report + No comment provided by engineer. + + + Archive report? + No comment provided by engineer. + + + Archive reports + swipe action + Archived contacts Архівні контакти @@ -1020,6 +1133,11 @@ Автоматичне прийняття зображень No comment provided by engineer. + + Auto-accept settings + Автоприйняття налаштувань + alert title + Back Назад @@ -1045,11 +1163,25 @@ Поганий хеш повідомлення No comment provided by engineer. + + Better calls + Кращі дзвінки + No comment provided by engineer. + Better groups Кращі групи No comment provided by engineer. + + Better groups performance + No comment provided by engineer. + + + Better message dates. + Кращі дати повідомлень. + No comment provided by engineer. + Better messages Кращі повідомлення @@ -1060,6 +1192,25 @@ Краща мережа No comment provided by engineer. + + Better notifications + Кращі сповіщення + No comment provided by engineer. + + + Better privacy and security + No comment provided by engineer. + + + Better security ✅ + Краща безпека ✅ + No comment provided by engineer. + + + Better user experience + Покращений користувацький досвід + No comment provided by engineer. + Black Чорний @@ -1140,11 +1291,31 @@ Болгарською, фінською, тайською та українською мовами - завдяки користувачам та [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Адреса підприємства + No comment provided by engineer. + + + Business chats + Ділові чати + No comment provided by engineer. + + + Businesses + 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: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Дзвінок вже закінчився! @@ -1193,7 +1364,8 @@ Cancel Скасувати - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1213,7 +1385,7 @@ Cannot receive file Не вдається отримати файл - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1230,6 +1402,15 @@ Зміна No comment provided by engineer. + + Change automatic message deletion? + alert title + + + Change chat profiles + Зміна профілів користувачів + authentication reason + Change database passphrase? Змінити пароль до бази даних? @@ -1274,11 +1455,21 @@ Change self-destruct passcode Змінити пароль самознищення authentication reason - set passcode view +set passcode view - - Chat archive - Архів чату + + Chat + Чат + No comment provided by engineer. + + + Chat already exists + Чат вже існує + No comment provided by engineer. + + + Chat already exists! + Чат вже існує! No comment provided by engineer. @@ -1341,20 +1532,50 @@ Налаштування чату No comment provided by engineer. + + Chat preferences were changed. + Змінено налаштування чату. + alert message + + + Chat profile + Профіль користувача + No comment provided by engineer. + Chat theme Тема чату No comment provided by engineer. + + 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. + Chats Чати No comment provided by engineer. + + Check messages every 20 min. + Перевіряйте повідомлення кожні 20 хв. + No comment provided by engineer. + + + Check messages when allowed. + Перевірте повідомлення, коли це дозволено. + No comment provided by engineer. + Check server address and try again. Перевірте адресу сервера та спробуйте ще раз. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1406,6 +1627,14 @@ Відверта розмова? No comment provided by engineer. + + Clear group? + No comment provided by engineer. + + + Clear or delete group? + No comment provided by engineer. + Clear private notes? Чисті приватні нотатки? @@ -1426,6 +1655,10 @@ Колірний режим No comment provided by engineer. + + Community guidelines violation + report reason + Compare file Порівняти файл @@ -1441,14 +1674,48 @@ Завершено No comment provided by engineer. + + Conditions accepted on: %@. + Умови приймаються на: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Для оператора(ів) приймаються умови: **%@**. + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + Умови вже прийняті для наступних операторів: **%@**. + No comment provided by engineer. + + + Conditions of use + Умови використання + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Для оператора(ів) приймаються умови: **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Умови приймаються на: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Умови будуть автоматично прийняті для увімкнених операторів на: %@. + No comment provided by engineer. + Configure ICE servers Налаштування серверів ICE No comment provided by engineer. - - Configured %@ servers - Налаштовані сервери %@ + + Configure server operators No comment provided by engineer. @@ -1501,6 +1768,10 @@ Підтвердити завантаження No comment provided by engineer. + + Confirmed + token status text + Connect Підключіться @@ -1620,6 +1891,10 @@ This is your own one-time link! Стан з'єднання та серверів. No comment provided by engineer. + + Connection blocked + No comment provided by engineer. + Connection error Помилка підключення @@ -1630,6 +1905,15 @@ This is your own one-time link! Помилка підключення (AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + No comment provided by engineer. + + + Connection not ready. + No comment provided by engineer. + Connection notifications Сповіщення про підключення @@ -1640,6 +1924,15 @@ This is your own one-time link! Запит на підключення відправлено! No comment provided by engineer. + + Connection requires encryption renegotiation. + No comment provided by engineer. + + + Connection security + Безпека з'єднання + No comment provided by engineer. + Connection terminated З'єднання розірвано @@ -1715,6 +2008,10 @@ This is your own one-time link! Контакти можуть позначати повідомлення для видалення; ви зможете їх переглянути. No comment provided by engineer. + + Content violates conditions of use + blocking reason + Continue Продовжуйте @@ -1740,6 +2037,11 @@ This is your own one-time link! Основна версія: v%@ No comment provided by engineer. + + Corner + Кут + No comment provided by engineer. + Correct name to %@? Виправити ім'я на %@? @@ -1750,6 +2052,11 @@ This is your own one-time link! Створити No comment provided by engineer. + + Create 1-time link + Створити одноразове посилання + No comment provided by engineer. + Create SimpleX address Створіть адресу SimpleX @@ -1760,11 +2067,6 @@ This is your own one-time link! Створіть групу, використовуючи випадковий профіль. No comment provided by engineer. - - Create an address to let people connect with you. - Створіть адресу, щоб люди могли з вами зв'язатися. - No comment provided by engineer. - Create file Створити файл @@ -1785,6 +2087,10 @@ This is your own one-time link! Створити посилання No comment provided by engineer. + + Create list + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 Створіть новий профіль у [desktop app](https://simplex.chat/downloads/). 💻 @@ -1825,11 +2131,6 @@ This is your own one-time link! Створено за адресою: %@ copied message info - - Created on %@ - Створено %@ - No comment provided by engineer. - Creating archive link Створення архівного посилання @@ -1845,6 +2146,11 @@ This is your own one-time link! Поточний пароль No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Текст поточних умов не вдалося завантажити, ви можете переглянути умови за цим посиланням: + No comment provided by engineer. + Current passphrase… Поточна парольна фраза… @@ -1865,6 +2171,11 @@ This is your own one-time link! Індивідуальний час No comment provided by engineer. + + Customizable message shape. + Налаштовується форма повідомлення. + No comment provided by engineer. + Customize theme Налаштувати тему @@ -1996,8 +2307,8 @@ This is your own one-time link! Delete Видалити - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? @@ -2034,14 +2345,13 @@ This is your own one-time link! Видалити та повідомити контакт No comment provided by engineer. - - Delete archive - Видалити архів + + Delete chat + Видалити чат No comment provided by engineer. - - Delete chat archive? - Видалити архів чату? + + Delete chat messages from your device. No comment provided by engineer. @@ -2054,6 +2364,11 @@ This is your own one-time link! Видалити профіль чату? No comment provided by engineer. + + Delete chat? + Видалити чат? + No comment provided by engineer. + Delete connection Видалити підключення @@ -2129,6 +2444,10 @@ This is your own one-time link! Видалити посилання? No comment provided by engineer. + + Delete list? + alert title + Delete member message? Видалити повідомлення учасника? @@ -2142,7 +2461,7 @@ This is your own one-time link! Delete messages Видалити повідомлення - No comment provided by engineer. + alert button Delete messages after @@ -2159,6 +2478,11 @@ This is your own one-time link! Видалити стару базу даних? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Видалити або модерувати до 200 повідомлень. + No comment provided by engineer. + Delete pending connection? Видалити очікуване з'єднання? @@ -2174,6 +2498,10 @@ This is your own one-time link! Видалити чергу server test step + + Delete report + No comment provided by engineer. + Delete up to 20 messages at once. Видаляйте до 20 повідомлень одночасно. @@ -2209,6 +2537,11 @@ This is your own one-time link! Помилки видалення No comment provided by engineer. + + Delivered even when Apple drops them. + Доставляються навіть тоді, коли Apple кидає їх. + No comment provided by engineer. + Delivery Доставка @@ -2309,8 +2642,13 @@ This is your own one-time link! Прямі повідомлення chat feature - - Direct messages between members are prohibited in this group. + + 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. @@ -2324,6 +2662,14 @@ This is your own one-time link! Вимкнути SimpleX Lock authentication reason + + Disable automatic message deletion? + alert title + + + Disable delete messages + alert button + Disable for all Вимкнути для всіх @@ -2349,8 +2695,8 @@ This is your own one-time link! Зникаючі повідомлення в цьому чаті заборонені. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. У цій групі заборонено зникаючі повідомлення. No comment provided by engineer. @@ -2409,6 +2755,15 @@ This is your own one-time link! Не надсилайте історію новим користувачам. No comment provided by engineer. + + Do not use credentials with proxy. + Не використовуйте облікові дані з проксі. + No comment provided by engineer. + + + Documents: + No comment provided by engineer. + Don't create address Не створювати адресу @@ -2419,11 +2774,19 @@ This is your own one-time link! Не вмикати No comment provided by engineer. + + Don't miss important messages. + No comment provided by engineer. + Don't show again Більше не показувати No comment provided by engineer. + + Done + No comment provided by engineer. + Downgrade and open chat Пониження та відкритий чат @@ -2432,7 +2795,8 @@ This is your own one-time link! Download Завантажити - chat item action + alert button +chat item action Download errors @@ -2449,6 +2813,11 @@ This is your own one-time link! Завантажити файл server test step + + Download files + Завантажити файли + alert action + Downloaded Завантажено @@ -2479,6 +2848,11 @@ This is your own one-time link! Тривалість No comment provided by engineer. + + E2E encrypted notifications. + Зашифровані сповіщення E2E. + No comment provided by engineer. + Edit Редагувати @@ -2499,6 +2873,10 @@ This is your own one-time link! Увімкнути (зберегти перевизначення) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + No comment provided by engineer. + Enable SimpleX Lock Увімкнути SimpleX Lock @@ -2512,7 +2890,7 @@ This is your own one-time link! Enable automatic message deletion? Увімкнути автоматичне видалення повідомлень? - No comment provided by engineer. + alert title Enable camera access @@ -2639,6 +3017,10 @@ This is your own one-time link! Повторне узгодження шифрування не вдалося. No comment provided by engineer. + + Encryption renegotiation in progress. + No comment provided by engineer. + Enter Passcode Введіть пароль @@ -2704,26 +3086,36 @@ This is your own one-time link! Помилка скасування зміни адреси No comment provided by engineer. + + Error accepting conditions + Помилка прийняття умов + alert title + Error accepting contact request Помилка при прийнятті запиту на контакт No comment provided by engineer. - - Error accessing database file - Помилка доступу до файлу бази даних - No comment provided by engineer. - Error adding member(s) Помилка додавання користувача(ів) No comment provided by engineer. + + Error adding server + Помилка додавання сервера + alert title + Error changing address Помилка зміни адреси No comment provided by engineer. + + Error changing connection profile + Помилка при зміні профілю з'єднання + No comment provided by engineer. + Error changing role Помилка зміни ролі @@ -2734,6 +3126,15 @@ This is your own one-time link! Помилка зміни налаштування No comment provided by engineer. + + Error changing to incognito! + Помилка переходу на інкогніто! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Помилка підключення до сервера переадресації %@. Спробуйте пізніше. @@ -2754,6 +3155,10 @@ This is your own one-time link! Помилка створення посилання на групу No comment provided by engineer. + + Error creating list + alert title + Error creating member contact Помилка при створенні контакту користувача @@ -2769,6 +3174,10 @@ This is your own one-time link! Помилка створення профілю! No comment provided by engineer. + + Error creating report + No comment provided by engineer. + Error decrypting file Помилка розшифрування файлу @@ -2849,9 +3258,14 @@ This is your own one-time link! Помилка приєднання до групи No comment provided by engineer. - - Error loading %@ servers - Помилка завантаження %@ серверів + + Error loading servers + Помилка завантаження серверів + alert title + + + Error migrating settings + Помилка міграції налаштувань No comment provided by engineer. @@ -2862,7 +3276,7 @@ This is your own one-time link! Error receiving file Помилка отримання файлу - No comment provided by engineer. + alert title Error reconnecting server @@ -2874,26 +3288,33 @@ This is your own one-time link! Помилка перепідключення серверів No comment provided by engineer. + + Error registering for notifications + alert title + Error removing member Помилка видалення учасника No comment provided by engineer. + + Error reordering lists + alert title + Error resetting statistics Статистика скидання помилок No comment provided by engineer. - - Error saving %@ servers - Помилка збереження %@ серверів - No comment provided by engineer. - Error saving ICE servers Помилка збереження серверів ICE No comment provided by engineer. + + Error saving chat list + alert title + Error saving group profile Помилка збереження профілю групи @@ -2909,6 +3330,11 @@ This is your own one-time link! Помилка збереження пароля на keychain No comment provided by engineer. + + Error saving servers + Сервери збереження помилок + alert title + Error saving settings Налаштування збереження помилок @@ -2954,16 +3380,25 @@ This is your own one-time link! Помилка зупинки чату No comment provided by engineer. + + Error switching profile + Помилка перемикання профілю + No comment provided by engineer. + Error switching profile! Помилка перемикання профілю! - No comment provided by engineer. + alertTitle Error synchronizing connection Помилка синхронізації з'єднання No comment provided by engineer. + + Error testing server connection + No comment provided by engineer. + Error updating group link Помилка оновлення посилання на групу @@ -2974,6 +3409,11 @@ This is your own one-time link! Повідомлення про помилку оновлення No comment provided by engineer. + + Error updating server + Помилка оновлення сервера + alert title + Error updating settings Помилка оновлення налаштувань @@ -3002,8 +3442,9 @@ This is your own one-time link! Error: %@ Помилка: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -3020,6 +3461,11 @@ This is your own one-time link! Помилки No comment provided by engineer. + + Errors in servers configuration. + Помилки в конфігурації серверів. + servers error + Even when disabled in the conversation. Навіть коли вимкнений у розмові. @@ -3035,6 +3481,10 @@ This is your own one-time link! Розгорнути chat item action + + Expired + token status text + Export database Експорт бази даних @@ -3075,20 +3525,44 @@ This is your own one-time link! Швидко і без очікування, поки відправник буде онлайн! No comment provided by engineer. + + Faster deletion of groups. + No comment provided by engineer. + Faster joining and more reliable messages. Швидше приєднання та надійніші повідомлення. No comment provided by engineer. + + Faster sending messages. + No comment provided by engineer. + Favorite Улюблений swipe action + + Favorites + No comment provided by engineer. + File error Помилка файлу - No comment provided by engineer. + file error alert title + + + File errors: +%@ + Помилки файлів: +%@ + alert message + + + File is blocked by server operator: +%@. + file error text File not found - most likely file was deleted or cancelled. @@ -3145,8 +3619,8 @@ This is your own one-time link! Файли і медіа chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Файли та медіа в цій групі заборонені. No comment provided by engineer. @@ -3215,21 +3689,69 @@ This is your own one-time link! Виправлення не підтримується учасником групи No comment provided by engineer. + + For all moderators + No comment provided by engineer. + + + For chat profile %@: + Для профілю чату %@: + servers error + For console Для консолі No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Наприклад, якщо ваш контакт отримує повідомлення через сервер SimpleX Chat, ваш додаток доставлятиме їх через сервер Flux. + No comment provided by engineer. + + + For me + No comment provided by engineer. + + + For private routing + Для приватної маршрутизації + No comment provided by engineer. + + + For social media + Для соціальних мереж + No comment provided by engineer. + Forward Пересилання chat item action + + Forward %d message(s)? + Переслати %d повідомлення(ь)? + alert title + Forward and save messages Пересилання та збереження повідомлень No comment provided by engineer. + + Forward messages + Пересилання повідомлень + alert action + + + Forward messages without files? + Пересилати повідомлення без файлів? + alert message + + + Forward up to 20 messages at once. + Пересилайте до 20 повідомлень одночасно. + No comment provided by engineer. + Forwarded Переслано @@ -3240,6 +3762,11 @@ This is your own one-time link! Переслано з No comment provided by engineer. + + Forwarding %lld messages + Пересилання повідомлень %lld + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше. @@ -3289,11 +3816,6 @@ Error: %2$@ Повне ім'я (необов'язково) No comment provided by engineer. - - Full name: - Повне ім'я: - No comment provided by engineer. - Fully decentralized – visible only to members. Повністю децентралізована - видима лише для учасників. @@ -3314,6 +3836,10 @@ Error: %2$@ GIF-файли та наклейки No comment provided by engineer. + + Get notified when mentioned. + No comment provided by engineer. + Good afternoon! Доброго дня! @@ -3379,41 +3905,6 @@ Error: %2$@ Групові посилання No comment provided by engineer. - - Group members can add message reactions. - Учасники групи можуть додавати реакції на повідомлення. - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) - No comment provided by engineer. - - - Group members can send SimpleX links. - Учасники групи можуть надсилати посилання SimpleX. - No comment provided by engineer. - - - Group members can send direct messages. - Учасники групи можуть надсилати прямі повідомлення. - No comment provided by engineer. - - - Group members can send disappearing messages. - Учасники групи можуть надсилати зникаючі повідомлення. - No comment provided by engineer. - - - Group members can send files and media. - Учасники групи можуть надсилати файли та медіа. - No comment provided by engineer. - - - Group members can send voice messages. - Учасники групи можуть надсилати голосові повідомлення. - No comment provided by engineer. - Group message: Групове повідомлення: @@ -3454,11 +3945,19 @@ Error: %2$@ Група буде видалена для вас - це не може бути скасовано! No comment provided by engineer. + + Groups + No comment provided by engineer. + Help Довідка No comment provided by engineer. + + Help admins moderating their groups. + No comment provided by engineer. + Hidden Приховано @@ -3509,10 +4008,19 @@ Error: %2$@ Як працює SimpleX No comment provided by engineer. + + How it affects privacy + Як це впливає на конфіденційність + No comment provided by engineer. + + + How it helps privacy + Як це захищає приватність + No comment provided by engineer. + How it works - Як це працює - No comment provided by engineer. + alert button How to @@ -3539,6 +4047,11 @@ Error: %2$@ Сервери ICE (по одному на лінію) No comment provided by engineer. + + IP address + IP-адреса + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Якщо ви не можете зустрітися особисто, покажіть QR-код у відеодзвінку або поділіться посиланням. @@ -3579,8 +4092,8 @@ Error: %2$@ Негайно No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Імунітет до спаму та зловживань No comment provided by engineer. @@ -3614,6 +4127,13 @@ Error: %2$@ Імпорт архіву No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Покращена доставка, зменшене використання трафіку. +Незабаром з'являться нові покращення! + No comment provided by engineer. + Improved message delivery Покращена доставка повідомлень @@ -3644,6 +4164,14 @@ Error: %2$@ Звуки вхідного дзвінка No comment provided by engineer. + + Inappropriate content + report reason + + + Inappropriate profile + report reason + Incognito Інкогніто @@ -3714,6 +4242,11 @@ Error: %2$@ Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Миттєво + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3721,11 +4254,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - Миттєво - No comment provided by engineer. - Interface Інтерфейс @@ -3736,6 +4264,26 @@ Error: %2$@ Кольори інтерфейсу No comment provided by engineer. + + Invalid + token status text + + + Invalid (bad token) + token status text + + + Invalid (expired) + token status text + + + Invalid (unregistered) + token status text + + + Invalid (wrong topic) + token status text + Invalid QR code Неправильний QR-код @@ -3774,7 +4322,7 @@ Error: %2$@ Invalid server address! Неправильна адреса сервера! - No comment provided by engineer. + alert title Invalid status @@ -3796,6 +4344,11 @@ Error: %2$@ Запросити учасників No comment provided by engineer. + + Invite to chat + Запросити в чат + No comment provided by engineer. + Invite to group Запросити до групи @@ -3811,8 +4364,8 @@ Error: %2$@ У цьому чаті заборонено безповоротне видалення повідомлень. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. У цій групі заборонено безповоротне видалення повідомлень. No comment provided by engineer. @@ -3902,7 +4455,7 @@ This is your link for group %@! Keep Тримай - No comment provided by engineer. + alert action Keep conversation @@ -3917,7 +4470,7 @@ This is your link for group %@! Keep unused invitation? Зберігати невикористані запрошення? - No comment provided by engineer. + alert title Keep your connections @@ -3954,6 +4507,16 @@ This is your link for group %@! Залишити swipe action + + Leave chat + Вийти з чату + No comment provided by engineer. + + + Leave chat? + Залишити чат? + No comment provided by engineer. + Leave group Покинути групу @@ -3994,6 +4557,18 @@ This is your link for group %@! Пов'язані робочі столи No comment provided by engineer. + + List + swipe action + + + List name and emoji should be different for all lists. + No comment provided by engineer. + + + List name... + No comment provided by engineer. + Live message! Живе повідомлення! @@ -4004,11 +4579,6 @@ This is your link for group %@! Живі повідомлення No comment provided by engineer. - - Local - Локально - No comment provided by engineer. - Local name Місцева назва @@ -4029,11 +4599,6 @@ This is your link for group %@! Режим блокування No comment provided by engineer. - - Make a private connection - Створіть приватне з'єднання - No comment provided by engineer. - Make one message disappear Зробити так, щоб одне повідомлення зникло @@ -4044,21 +4609,11 @@ This is your link for group %@! Зробіть профіль приватним! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Переконайтеся, що адреси серверів %@ мають правильний формат, розділені рядками і не дублюються (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Переконайтеся, що адреси серверів WebRTC ICE мають правильний формат, розділені рядками і не дублюються. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Багато людей запитували: *якщо SimpleX не має ідентифікаторів користувачів, як він може доставляти повідомлення?* - No comment provided by engineer. - Mark deleted for everyone Позначити видалено для всіх @@ -4104,6 +4659,15 @@ This is your link for group %@! Користувач неактивний item status text + + Member reports + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + Роль учасника буде змінено на "%@". Усі учасники чату отримають сповіщення. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Роль учасника буде змінено на "%@". Всі учасники групи будуть повідомлені про це. @@ -4114,11 +4678,59 @@ This is your link for group %@! Роль учасника буде змінено на "%@". Учасник отримає нове запрошення. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Учасника буде видалено з чату – це неможливо скасувати! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Учасник буде видалений з групи - це неможливо скасувати! No comment provided by engineer. + + Members can add message reactions. + Учасники групи можуть додавати реакції на повідомлення. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) + No comment provided by engineer. + + + Members can report messsages to moderators. + No comment provided by engineer. + + + Members can send SimpleX links. + Учасники групи можуть надсилати посилання SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Учасники групи можуть надсилати прямі повідомлення. + No comment provided by engineer. + + + Members can send disappearing messages. + Учасники групи можуть надсилати зникаючі повідомлення. + No comment provided by engineer. + + + Members can send files and media. + Учасники групи можуть надсилати файли та медіа. + No comment provided by engineer. + + + Members can send voice messages. + Учасники групи можуть надсилати голосові повідомлення. + No comment provided by engineer. + + + Mention members 👋 + No comment provided by engineer. + Menus Меню @@ -4169,8 +4781,8 @@ This is your link for group %@! Реакції на повідомлення в цьому чаті заборонені. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Реакції на повідомлення в цій групі заборонені. No comment provided by engineer. @@ -4184,6 +4796,11 @@ This is your link for group %@! Сервери повідомлень No comment provided by engineer. + + Message shape + Форма повідомлення + No comment provided by engineer. + Message source remains private. Джерело повідомлення залишається приватним. @@ -4224,6 +4841,10 @@ This is your link for group %@! Повідомлення від %@ будуть показані! No comment provided by engineer. + + Messages in this chat will never be deleted. + alert message + Messages received Отримані повідомлення @@ -4234,6 +4855,11 @@ This is your link for group %@! Надіслані повідомлення No comment provided by engineer. + + Messages were deleted after you selected them. + Повідомлення були видалені після того, як ви їх вибрали. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Повідомлення, файли та дзвінки захищені **наскрізним шифруванням** з ідеальною секретністю переадресації, відмовою та відновленням після злому. @@ -4299,9 +4925,9 @@ This is your link for group %@! Міграцію завершено No comment provided by engineer. - - Migrations: %@ - Міграції: %@ + + Migrations: + Міграції: No comment provided by engineer. @@ -4319,6 +4945,10 @@ This is your link for group %@! Модерується за: %@ copied message info + + More + swipe action + More improvements are coming soon! Незабаром буде ще більше покращень! @@ -4329,6 +4959,11 @@ This is your link for group %@! Більш надійне з'єднання з мережею. No comment provided by engineer. + + More reliable notifications + Більш надійні сповіщення + No comment provided by engineer. + Most likely this connection is deleted. Швидше за все, це з'єднання видалено. @@ -4342,7 +4977,11 @@ This is your link for group %@! Mute Вимкнути звук - swipe action + notification label action + + + Mute all + notification label action Muted when inactive! @@ -4364,6 +5003,11 @@ This is your link for group %@! Підключення до мережі No comment provided by engineer. + + Network decentralization + Децентралізація мережі + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його. @@ -4374,6 +5018,11 @@ This is your link for group %@! Керування мережею No comment provided by engineer. + + Network operator + Мережевий оператор + No comment provided by engineer. + Network settings Налаштування мережі @@ -4384,11 +5033,25 @@ This is your link for group %@! Стан мережі No comment provided by engineer. + + New + token status text + New Passcode Новий пароль No comment provided by engineer. + + 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. + No comment provided by engineer. + New chat Новий чат @@ -4409,11 +5072,6 @@ This is your link for group %@! Новий контакт: notification - - New database archive - Новий архів бази даних - No comment provided by engineer. - New desktop app! Новий десктопний додаток! @@ -4424,6 +5082,11 @@ This is your link for group %@! Нове ім'я відображення No comment provided by engineer. + + New events + Нові події + notification + New in %@ Нове в %@ @@ -4449,6 +5112,11 @@ This is your link for group %@! Новий пароль… No comment provided by engineer. + + New server + Новий сервер + No comment provided by engineer. + No Ні @@ -4459,6 +5127,18 @@ This is your link for group %@! Немає пароля програми Authentication unavailable + + No chats + No comment provided by engineer. + + + No chats found + No comment provided by engineer. + + + No chats in list %@ + No comment provided by engineer. + No contacts selected Не вибрано жодного контакту @@ -4504,31 +5184,102 @@ This is your link for group %@! Немає інформації, спробуйте перезавантажити No comment provided by engineer. + + No media & file servers. + Ніяких медіа та файлових серверів. + servers error + + + No message + No comment provided by engineer. + + + No message servers. + Ніяких серверів повідомлень. + servers error + No network connection Немає підключення до мережі No comment provided by engineer. + + No permission to record speech + Немає дозволу на запис промови + No comment provided by engineer. + + + No permission to record video + Немає дозволу на запис відео + No comment provided by engineer. + No permission to record voice message Немає дозволу на запис голосового повідомлення No comment provided by engineer. + + No push server + Локально + No comment provided by engineer. + No received or sent files Немає отриманих або відправлених файлів No comment provided by engineer. + + No servers for private message routing. + Немає серверів для маршрутизації приватних повідомлень. + servers error + + + No servers to receive files. + Немає серверів для отримання файлів. + servers error + + + No servers to receive messages. + Немає серверів для отримання повідомлень. + servers error + + + No servers to send files. + Немає серверів для надсилання файлів. + servers error + + + No token! + alert title + + + No unread chats + No comment provided by engineer. + + + No user identifiers. + Ніяких ідентифікаторів користувачів. + No comment provided by engineer. + Not compatible! Не сумісні! No comment provided by engineer. + + Notes + No comment provided by engineer. + Nothing selected Нічого не вибрано No comment provided by engineer. + + Nothing to forward! + Нічого пересилати! + alert title + Notifications Сповіщення @@ -4539,6 +5290,19 @@ This is your link for group %@! Сповіщення вимкнено! No comment provided by engineer. + + Notifications error + alert title + + + Notifications privacy + Сповіщення про приватність + No comment provided by engineer. + + + Notifications status + alert title + Now admins can: - delete members' messages. @@ -4561,18 +5325,13 @@ This is your link for group %@! Ok Гаразд - No comment provided by engineer. + alert button Old database Стара база даних No comment provided by engineer. - - Old database archive - Старий архів бази даних - No comment provided by engineer. - One-time invitation link Посилання на одноразове запрошення @@ -4597,8 +5356,13 @@ Requires compatible VPN. Onion хости не будуть використовуватися. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + Лише власники чату можуть змінювати налаштування. + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. Тільки клієнтські пристрої зберігають профілі користувачів, контакти, групи та повідомлення, надіслані за допомогою **2-шарового наскрізного шифрування**. No comment provided by engineer. @@ -4622,6 +5386,14 @@ Requires compatible VPN. Тільки власники груп можуть вмикати голосові повідомлення. No comment provided by engineer. + + Only sender and moderators see it + No comment provided by engineer. + + + Only you and moderators see it + No comment provided by engineer. + Only you can add message reactions. Тільки ви можете додавати реакції на повідомлення. @@ -4675,13 +5447,18 @@ Requires compatible VPN. Open Відкрито - No comment provided by engineer. + alert action Open Settings Відкрийте Налаштування No comment provided by engineer. + + Open changes + Відкриті зміни + No comment provided by engineer. + Open chat Відкритий чат @@ -4692,36 +5469,45 @@ Requires compatible VPN. Відкрийте консоль чату authentication reason + + Open conditions + Відкриті умови + No comment provided by engineer. + Open group Відкрита група No comment provided by engineer. + + Open link? + alert title + Open migration to another device Відкрита міграція на інший пристрій authentication reason - - Open server settings - Відкрити налаштування сервера - No comment provided by engineer. - - - Open user profiles - Відкрити профілі користувачів - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Протокол і код з відкритим вихідним кодом - будь-хто може запускати сервери. - No comment provided by engineer. - Opening app… Відкриваємо програму… No comment provided by engineer. + + Operator + Оператор + No comment provided by engineer. + + + Operator server + Сервер оператора + alert title + + + Or import archive file + Або імпортуйте архівний файл + No comment provided by engineer. + Or paste archive link Або вставте посилання на архів @@ -4742,15 +5528,26 @@ Requires compatible VPN. Або покажіть цей код No comment provided by engineer. + + Or to share privately + Або поділитися приватно + No comment provided by engineer. + + + Organize chats into lists + No comment provided by engineer. + Other Інше No comment provided by engineer. - - Other %@ servers - Інші сервери %@ - No comment provided by engineer. + + Other file errors: +%@ + Інші помилки файлів: +%@ + alert message PING count @@ -4787,6 +5584,11 @@ Requires compatible VPN. Пароль встановлено! No comment provided by engineer. + + Password + Пароль + No comment provided by engineer. + Password to show Показати пароль @@ -4822,13 +5624,8 @@ Requires compatible VPN. В очікуванні No comment provided by engineer. - - People can connect to you only via the links you share. - Люди можуть зв'язатися з вами лише за посиланнями, якими ви ділитеся. - No comment provided by engineer. - - - Periodically + + Periodic Періодично No comment provided by engineer. @@ -4931,11 +5728,28 @@ Error: %@ Будь ласка, зберігайте пароль надійно, ви НЕ зможете змінити його, якщо втратите. No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface Польський інтерфейс No comment provided by engineer. + + Port + Порт + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Можливо, в адресі сервера неправильно вказано відбиток сертифіката @@ -4946,16 +5760,16 @@ Error: %@ Зберегти чернетку останнього повідомлення з вкладеннями. No comment provided by engineer. - - Preset server - Попередньо встановлений сервер - No comment provided by engineer. - Preset server address Попередньо встановлена адреса сервера No comment provided by engineer. + + Preset servers + Попередньо встановлені сервери + No comment provided by engineer. + Preview Попередній перегляд @@ -4971,16 +5785,33 @@ Error: %@ Конфіденційність і безпека No comment provided by engineer. + + Privacy for your customers. + Конфіденційність для ваших клієнтів. + 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 Приватні імена файлів No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing Маршрутизація приватних повідомлень @@ -5021,16 +5852,6 @@ Error: %@ Зображення профілю No comment provided by engineer. - - Profile name - Назва профілю - No comment provided by engineer. - - - Profile name: - Ім'я профілю: - No comment provided by engineer. - Profile password Пароль до профілю @@ -5044,7 +5865,7 @@ Error: %@ Profile update will be sent to your contacts. Оновлення профілю буде надіслано вашим контактам. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5066,6 +5887,10 @@ Error: %@ Заборонити реакції на повідомлення. No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. Заборонити надсилання посилань SimpleX. @@ -5133,9 +5958,14 @@ Enable in *Network & servers* settings. Проксі-сервери No comment provided by engineer. + + Proxy requires password + Проксі вимагає пароль + No comment provided by engineer. + Push notifications - Push-повідомлення + Push-сповіщення No comment provided by engineer. @@ -5173,26 +6003,21 @@ Enable in *Network & servers* settings. Читати далі No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 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). 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. - Читайте більше в нашому репозиторії на GitHub. - 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). @@ -5323,11 +6148,23 @@ Enable in *Network & servers* settings. Зменшення використання акумулятора No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject Відхилити reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5354,6 +6191,11 @@ Enable in *Network & servers* settings. Видалити No comment provided by engineer. + + Remove archive? + Видалити архів? + No comment provided by engineer. + Remove image Видалити зображення @@ -5419,6 +6261,46 @@ Enable in *Network & servers* settings. Відповісти chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required Потрібно @@ -5504,6 +6386,11 @@ Enable in *Network & servers* settings. Показувати chat item action + + Review conditions + Умови перегляду + No comment provided by engineer. + Revoke Відкликати @@ -5534,6 +6421,11 @@ Enable in *Network & servers* settings. Сервер SMP No comment provided by engineer. + + SOCKS proxy + Проксі SOCKS + No comment provided by engineer. + Safely receive files Безпечне отримання файлів @@ -5547,17 +6439,18 @@ Enable in *Network & servers* settings. Save Зберегти - chat item action + alert button +chat item action Save (and notify contacts) Зберегти (і повідомити контактам) - No comment provided by engineer. + alert button Save and notify contact Зберегти та повідомити контакт - No comment provided by engineer. + alert button Save and notify group members @@ -5574,21 +6467,15 @@ Enable in *Network & servers* settings. Збереження та оновлення профілю групи No comment provided by engineer. - - Save archive - Зберегти архів - No comment provided by engineer. - - - Save auto-accept settings - Зберегти налаштування автоприйому - No comment provided by engineer. - Save group profile Зберегти профіль групи No comment provided by engineer. + + Save list + No comment provided by engineer. + Save passphrase and open chat Збережіть пароль і відкрийте чат @@ -5602,7 +6489,7 @@ Enable in *Network & servers* settings. Save preferences? Зберегти настройки? - No comment provided by engineer. + alert title Save profile password @@ -5617,18 +6504,18 @@ Enable in *Network & servers* settings. Save servers? Зберегти сервери? - No comment provided by engineer. - - - Save settings? - Зберегти налаштування? - No comment provided by engineer. + alert title Save welcome message? Зберегти вітальне повідомлення? No comment provided by engineer. + + Save your profile? + Зберегти свій профіль? + alert title + Saved Збережено @@ -5649,6 +6536,11 @@ Enable in *Network & servers* settings. Збережене повідомлення message info title + + Saving %lld messages + Збереження повідомлень %lld + No comment provided by engineer. + Scale Масштаб @@ -5729,6 +6621,11 @@ Enable in *Network & servers* settings. Виберіть chat item action + + Select chat profile + Виберіть профіль чату + No comment provided by engineer. + Selected %lld Вибрано %lld @@ -5819,9 +6716,8 @@ Enable in *Network & servers* settings. Надсилати сповіщення No comment provided by engineer. - - Send notifications: - Надсилати сповіщення: + + Send private reports No comment provided by engineer. @@ -5847,7 +6743,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Відправник скасував передачу файлу. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5944,6 +6840,16 @@ Enable in *Network & servers* settings. Відправлено через проксі No comment provided by engineer. + + Server + Сервер + No comment provided by engineer. + + + Server added to operator %@. + Сервер додано до оператора %@. + alert message + Server address Адреса сервера @@ -5959,6 +6865,21 @@ Enable in *Network & servers* settings. Адреса сервера несумісна з налаштуваннями мережі: %@. No comment provided by engineer. + + Server operator changed. + Оператор сервера змінився. + alert title + + + Server operators + Оператори серверів + No comment provided by engineer. + + + Server protocol changed. + Протокол сервера змінено. + alert title + Server requires authorization to create queues, check password Сервер вимагає авторизації для створення черг, перевірте пароль @@ -6014,6 +6935,10 @@ Enable in *Network & servers* settings. Встановити 1 день No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… Встановити ім'я контакту… @@ -6034,6 +6959,10 @@ Enable in *Network & servers* settings. Встановіть його замість аутентифікації системи. No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode Встановити пароль @@ -6064,6 +6993,11 @@ Enable in *Network & servers* settings. Налаштування No comment provided by engineer. + + Settings were changed. + Налаштування були змінені. + alert message + Shape profile images Сформуйте зображення профілю @@ -6072,22 +7006,38 @@ Enable in *Network & servers* settings. Share Поділіться - chat item action + alert action +chat item action Share 1-time link Поділитися 1-разовим посиланням No comment provided by engineer. + + Share 1-time link with a friend + Поділіться одноразовим посиланням з другом + No comment provided by engineer. + + + Share SimpleX address on social media. + Поділіться адресою SimpleX у соціальних мережах. + No comment provided by engineer. + Share address Поділитися адресою No comment provided by engineer. + + Share address publicly + Поділіться адресою публічно + No comment provided by engineer. + Share address with contacts? Поділіться адресою з контактами? - No comment provided by engineer. + alert title Share from other apps. @@ -6099,6 +7049,11 @@ Enable in *Network & servers* settings. Поділіться посиланням No comment provided by engineer. + + Share profile + Поділіться профілем + No comment provided by engineer. + Share this 1-time invite link Поділіться цим одноразовим посиланням-запрошенням @@ -6114,6 +7069,10 @@ Enable in *Network & servers* settings. Поділіться з контактами No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Показати QR-код @@ -6169,6 +7128,11 @@ Enable in *Network & servers* settings. Адреса SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat і Flux уклали угоду про включення серверів, керованих Flux, у додаток. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Безпека SimpleX Chat була перевірена компанією Trail of Bits. @@ -6199,6 +7163,20 @@ Enable in *Network & servers* settings. Адреса SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + SimpleX-адреси та одноразові посилання можна безпечно ділитися через будь-який месенджер. + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX адреса або одноразове посилання? + No comment provided by engineer. + + + SimpleX channel link + simplex link type + SimpleX contact address Контактна адреса SimpleX @@ -6219,8 +7197,8 @@ Enable in *Network & servers* settings. Посилання SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. У цій групі заборонені посилання на SimpleX. No comment provided by engineer. @@ -6234,6 +7212,11 @@ Enable in *Network & servers* settings. Одноразове запрошення SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + Протоколи SimpleX, розглянуті Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Спрощений режим інкогніто @@ -6264,6 +7247,11 @@ Enable in *Network & servers* settings. М'який blur media + + Some app settings were not migrated. + Деякі налаштування програми не були перенесені. + No comment provided by engineer. + Some file(s) were not exported: Деякі файли не було експортовано: @@ -6279,11 +7267,23 @@ Enable in *Network & servers* settings. Під час імпорту виникли деякі несмертельні помилки: No comment provided by engineer. + + Some servers failed the test: +%@ + Деякі сервери не пройшли тестування: +%@ + alert message + Somebody Хтось notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. Квадрат, коло або щось середнє між ними. @@ -6329,11 +7329,6 @@ Enable in *Network & servers* settings. Припинити чат No comment provided by engineer. - - Stop chat to enable database actions - Зупиніть чат, щоб увімкнути дії з базою даних - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Зупиніть чат, щоб експортувати, імпортувати або видалити базу даних чату. Ви не зможете отримувати та надсилати повідомлення, поки чат зупинено. @@ -6362,18 +7357,22 @@ Enable in *Network & servers* settings. Stop sharing Припиніть ділитися - No comment provided by engineer. + alert action Stop sharing address? Припинити ділитися адресою? - No comment provided by engineer. + alert title Stopping chat Зупинка чату No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong Сильний @@ -6404,6 +7403,16 @@ Enable in *Network & servers* settings. Підтримка чату SimpleX No comment provided by engineer. + + Switch audio and video during the call. + Перемикайте аудіо та відео під час дзвінка. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Переключіть профіль чату для отримання одноразових запрошень. + No comment provided by engineer. + System Система @@ -6424,6 +7433,10 @@ Enable in *Network & servers* settings. Тайм-аут TCP-з'єднання No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6439,11 +7452,21 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Хвіст + No comment provided by engineer. + Take picture Сфотографуйте No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше. + No comment provided by engineer. + Tap button Натисніть кнопку @@ -6482,13 +7505,17 @@ Enable in *Network & servers* settings. Temporary file error Тимчасова помилка файлу - No comment provided by engineer. + file error alert title Test failed at step %@. Тест завершився невдало на кроці %@. server test failure + + Test notifications + No comment provided by engineer. + Test server Тестовий сервер @@ -6502,7 +7529,7 @@ Enable in *Network & servers* settings. Tests failed! Тести не пройшли! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6519,11 +7546,6 @@ Enable in *Network & servers* settings. Дякуємо користувачам - зробіть свій внесок через Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Перша платформа без жодних ідентифікаторів користувачів – приватна за дизайном. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6536,6 +7558,11 @@ It can happen because of some bug or when the connection is compromised.Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + Додаток захищає вашу конфіденційність, використовуючи різних операторів у кожній розмові. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Програма попросить підтвердити завантаження з невідомих файлових серверів (крім .onion). @@ -6551,6 +7578,11 @@ 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, your contact may be offline. + З'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн. + No comment provided by engineer. + The connection you accepted will be cancelled! Прийняте вами з'єднання буде скасовано! @@ -6571,6 +7603,11 @@ It can happen because of some bug or when the connection is compromised.Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з'єднання! No comment provided by engineer. + + The future of messaging + Наступне покоління приватних повідомлень + No comment provided by engineer. + The hash of the previous message is different. Хеш попереднього повідомлення відрізняється. @@ -6596,19 +7633,19 @@ It can happen because of some bug or when the connection is compromised.Повідомлення будуть позначені як модеровані для всіх учасників. No comment provided by engineer. - - The next generation of private messaging - Наступне покоління приватних повідомлень - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Стара база даних не була видалена під час міграції, її можна видалити. No comment provided by engineer. - - The profile is only shared with your contacts. - Профіль доступний лише вашим контактам. + + The same conditions will apply to operator **%@**. + Такі ж умови діятимуть і для оператора **%@**. + No comment provided by engineer. + + + The second preset operator in the app! + Другий попередньо встановлений оператор у застосунку! No comment provided by engineer. @@ -6626,16 +7663,31 @@ It can happen because of some bug or when the connection is compromised.Сервери для нових підключень вашого поточного профілю чату **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + Сервери для нових файлів вашого поточного профілю чату **%@**. + No comment provided by engineer. + 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. + Завантажений архів бази даних буде назавжди видалено з серверів. + No comment provided by engineer. + Themes Теми No comment provided by engineer. + + These conditions will also apply for: **%@**. + Ці умови також поширюються на: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Ці налаштування стосуються вашого поточного профілю **%@**. @@ -6656,6 +7708,10 @@ It can happen because of some bug or when the connection is compromised.Цю дію неможливо скасувати - повідомлення, надіслані та отримані раніше, ніж вибрані, будуть видалені. Це може зайняти кілька хвилин. No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Цю дію неможливо скасувати - ваш профіль, контакти, повідомлення та файли будуть безповоротно втрачені. @@ -6701,11 +7757,19 @@ It can happen because of some bug or when the connection is compromised.Це ваше власне одноразове посилання! 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. + No comment provided by engineer. + 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. + No comment provided by engineer. + This setting applies to messages in your current chat profile **%@**. Це налаштування застосовується до повідомлень у вашому поточному профілі чату **%@**. @@ -6736,9 +7800,9 @@ It can happen because of some bug or when the connection is compromised.Щоб створити нове з'єднання No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів. + + To protect against your link being replaced, you can compare contact security codes. + Щоб захиститися від заміни вашого посилання, ви можете порівняти коди безпеки контактів. No comment provided by engineer. @@ -6758,6 +7822,26 @@ You will be prompted to complete authentication before this feature is enabled.< Перед увімкненням цієї функції вам буде запропоновано пройти автентифікацію. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів. + No comment provided by engineer. + + + To receive + Щоб отримати + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + Для запису промови, будь ласка, надайте дозвіл на використання мікрофону. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + Для запису відео, будь ласка, надайте дозвіл на використання камери. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. Щоб записати голосове повідомлення, будь ласка, надайте дозвіл на використання мікрофону. @@ -6768,11 +7852,21 @@ You will be prompted to complete authentication before this feature is enabled.< Щоб відкрити свій прихований профіль, введіть повний пароль у поле пошуку на сторінці **Ваші профілі чату**. No comment provided by engineer. + + To send + Щоб відправити + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Для підтримки миттєвих push-повідомлень необхідно перенести базу даних чату. No comment provided by engineer. + + 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. Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях. @@ -6788,6 +7882,10 @@ You will be prompted to complete authentication before this feature is enabled.< Увімкніть інкогніто при підключенні. No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity Непрозорість панелі інструментів @@ -6863,6 +7961,11 @@ You will be prompted to complete authentication before this feature is enabled.< Розблокувати учасника? No comment provided by engineer. + + Undelivered messages + Недоставлені повідомлення + No comment provided by engineer. + Unexpected migration state Неочікуваний стан міграції @@ -6911,7 +8014,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Невідомі сервери! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6948,13 +8051,17 @@ To connect, please ask your contact to create another connection link and check Unmute Увімкнути звук - swipe action + notification label action Unread Непрочитане swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Новим користувачам надсилається до 100 останніх повідомлень. @@ -6980,6 +8087,10 @@ To connect, please ask your contact to create another connection link and check Оновити налаштування? No comment provided by engineer. + + Updated conditions + No comment provided by engineer. + Updating settings will re-connect the client to all servers. Оновлення налаштувань призведе до перепідключення клієнта до всіх серверів. @@ -7020,16 +8131,34 @@ To connect, please ask your contact to create another connection link and check Завантаження архіву No comment provided by engineer. + + Use %@ + Використовуйте %@ + No comment provided by engineer. + Use .onion hosts Використовуйте хости .onion No comment provided by engineer. + + Use SOCKS proxy + Використовуйте SOCKS проксі + No comment provided by engineer. + Use SimpleX Chat servers? Використовувати сервери SimpleX Chat? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Використовуйте чат @@ -7040,6 +8169,16 @@ To connect, please ask your contact to create another connection link and check Використовувати поточний профіль No comment provided by engineer. + + Use for files + Використовуйте для файлів + No comment provided by engineer. + + + Use for messages + Використовуйте для повідомлень + No comment provided by engineer. + Use for new connections Використовуйте для нових з'єднань @@ -7080,6 +8219,15 @@ To connect, please ask your contact to create another connection link and check Використовувати сервер No comment provided by engineer. + + Use servers + Використовуйте сервери + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Використовуйте додаток під час розмови. @@ -7090,9 +8238,8 @@ To connect, please ask your contact to create another connection link and check Використовуйте додаток однією рукою. No comment provided by engineer. - - User profile - Профіль користувача + + Use web port No comment provided by engineer. @@ -7100,6 +8247,11 @@ To connect, please ask your contact to create another connection link and check Вибір користувача No comment provided by engineer. + + Username + Ім'я користувача + No comment provided by engineer. + Using SimpleX Chat servers. Використання серверів SimpleX Chat. @@ -7170,11 +8322,21 @@ To connect, please ask your contact to create another connection link and check Відео та файли до 1 Гб No comment provided by engineer. + + View conditions + Умови перегляду + No comment provided by engineer. + View security code Переглянути код безпеки No comment provided by engineer. + + View updated conditions + Переглянути оновлені умови + No comment provided by engineer. + Visible history Видима історія @@ -7190,8 +8352,8 @@ To connect, please ask your contact to create another connection link and check Голосові повідомлення в цьому чаті заборонені. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Голосові повідомлення в цій групі заборонені. No comment provided by engineer. @@ -7285,9 +8447,9 @@ To connect, please ask your contact to create another connection link and check При підключенні аудіо та відеодзвінків. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Коли люди звертаються із запитом на підключення, ви можете прийняти або відхилити його. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Коли увімкнено більше одного оператора, жоден з них не має метаданих, щоб дізнатися, хто з ким спілкується. No comment provided by engineer. @@ -7333,7 +8495,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Без Tor або VPN ваша IP-адреса буде видимою для цих XFTP-ретрансляторів: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7360,11 +8522,6 @@ To connect, please ask your contact to create another connection link and check XFTP-сервер No comment provided by engineer. - - You - Ти - No comment provided by engineer. - You **must not** use the same database on two devices. Ви **не повинні використовувати** одну і ту ж базу даних на двох пристроях. @@ -7390,6 +8547,11 @@ To connect, please ask your contact to create another connection link and check Ви вже підключені до %@. No comment provided by engineer. + + You are already connected with %@. + Ви вже підключені до %@. + No comment provided by engineer. + You are already connecting to %@. Ви вже з'єднані з %@. @@ -7452,6 +8614,11 @@ Repeat join request? Ви можете змінити його в налаштуваннях зовнішнього вигляду. No comment provided by engineer. + + You can configure servers via settings. + Ви можете налаштувати сервери за допомогою налаштувань. + No comment provided by engineer. + You can create it later Ви можете створити його пізніше @@ -7492,6 +8659,11 @@ Repeat join request? Ви можете надсилати повідомлення на %@ з архівних контактів. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + Ви можете задати ім'я з'єднання, щоб запам'ятати, з ким ви поділилися посиланням. + No comment provided by engineer. + You can set lock screen notification preview via settings. Ви можете налаштувати попередній перегляд сповіщень на екрані блокування за допомогою налаштувань. @@ -7507,11 +8679,6 @@ Repeat join request? Ви можете поділитися цією адресою зі своїми контактами, щоб вони могли зв'язатися з **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Ви можете поділитися своєю адресою у вигляді посилання або QR-коду - будь-хто зможе зв'язатися з вами. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Запустити чат можна через Налаштування програми / База даних або перезапустивши програму @@ -7535,23 +8702,23 @@ Repeat join request? You can view invitation link again in connection details. Ви можете переглянути посилання на запрошення ще раз у деталях підключення. - No comment provided by engineer. + alert message You can't send messages! Ви не можете надсилати повідомлення! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Ви контролюєте, через який(і) сервер(и) **отримувати** повідомлення, ваші контакти - сервери, які ви використовуєте для надсилання їм повідомлень. - 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 already requested connection via this address! Ви вже надсилали запит на підключення за цією адресою! @@ -7619,6 +8786,10 @@ Repeat connection request? Ви надіслали запрошення до групи No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! Ви будете підключені до групи, коли пристрій господаря групи буде в мережі, будь ласка, зачекайте або перевірте пізніше! @@ -7654,6 +8825,11 @@ Repeat connection request? Ви все одно отримуватимете дзвінки та сповіщення від вимкнених профілів, якщо вони активні. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + Ви більше не будете отримувати повідомлення з цього чату. Історія чату буде збережена. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Ви перестанете отримувати повідомлення від цієї групи. Історія чату буде збережена. @@ -7674,31 +8850,16 @@ Repeat connection request? Ви використовуєте профіль інкогніто для цієї групи - щоб запобігти поширенню вашого основного профілю, запрошення контактів заборонено No comment provided by engineer. - - Your %@ servers - Ваші сервери %@ - No comment provided by engineer. - Your ICE servers Ваші сервери ICE No comment provided by engineer. - - Your SMP servers - Ваші SMP-сервери - No comment provided by engineer. - Your SimpleX address Ваша адреса SimpleX No comment provided by engineer. - - Your XFTP servers - Ваші XFTP-сервери - No comment provided by engineer. - Your calls Твої дзвінки @@ -7714,11 +8875,21 @@ Repeat connection request? Ваша база даних чату не зашифрована - встановіть ключову фразу, щоб зашифрувати її. No comment provided by engineer. + + Your chat preferences + Ваші налаштування чату + alert title + Your chat profiles Ваші профілі чату No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ваше з'єднання було переміщено на %@, але під час перенаправлення на профіль сталася несподівана помилка. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ваш контакт надіслав файл, розмір якого перевищує підтримуваний на цей момент максимальний розмір (%@). @@ -7734,6 +8905,11 @@ Repeat connection request? Ваші контакти залишаться на зв'язку. 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. Ваша поточна база даних чату буде ВИДАЛЕНА і ЗАМІНЕНА імпортованою. @@ -7764,33 +8940,36 @@ Repeat connection request? Ваш профіль **%@** буде опублікований. 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 only shared with your contacts. + Профіль доступний лише вашим контактам. No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери 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 random profile Ваш випадковий профіль No comment provided by engineer. - - Your server - Ваш сервер - No comment provided by engineer. - Your server address Адреса вашого сервера No comment provided by engineer. + + Your servers + Ваші сервери + No comment provided by engineer. + Your settings Ваші налаштування @@ -7831,6 +9010,11 @@ SimpleX servers cannot see your profile. прийнято виклик call status + + accepted invitation + прийняте запрошення + chat list item title + admin адмін @@ -7866,6 +9050,10 @@ SimpleX servers cannot see your profile. та %lld інших подій No comment provided by engineer. + + archived report + No comment provided by engineer. + attempts спроби @@ -7904,7 +9092,8 @@ SimpleX servers cannot see your profile. blocked by admin заблоковано адміністратором - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8019,7 +9208,7 @@ SimpleX servers cannot see your profile. connecting… з'єднання… - chat list item title + No comment provided by engineer. connection established @@ -8074,7 +9263,8 @@ SimpleX servers cannot see your profile. default (%@) за замовчуванням (%@) - pref value + delete after time +pref value default (no) @@ -8201,11 +9391,6 @@ SimpleX servers cannot see your profile. помилка No comment provided by engineer. - - event happened - відбулася подія - No comment provided by engineer. - expired закінчився @@ -8376,20 +9561,19 @@ SimpleX servers cannot see your profile. модерується %@ marked deleted chat item preview text + + moderator + member role + months місяців time unit - - mute - приглушити - No comment provided by engineer. - never ніколи - No comment provided by engineer. + delete after time new message @@ -8420,8 +9604,8 @@ SimpleX servers cannot see your profile. off вимкнено enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8463,6 +9647,14 @@ SimpleX servers cannot see your profile. одноранговий No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption квантово-стійке шифрування e2e @@ -8478,6 +9670,10 @@ SimpleX servers cannot see your profile. отримали підтвердження… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call відхилений виклик @@ -8508,6 +9704,11 @@ SimpleX servers cannot see your profile. прибрали вас rcv group event chat item + + requested to connect + запит на підключення + chat list item title + saved збережено @@ -8607,11 +9808,6 @@ last received msg: %2$@ невідомий статус No comment provided by engineer. - - unmute - увімкнути звук - No comment provided by engineer. - unprotected незахищені @@ -8776,7 +9972,7 @@ last received msg: %2$@
- +
@@ -8813,7 +10009,7 @@ last received msg: %2$@
- +
@@ -8833,9 +10029,40 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + %d нових подій + notification body + + + From %d chat(s) + notification body + + + From: %@ + Від: %@ + notification body + + + New events + Нові події + notification + + + New messages + Нові повідомлення + notification + + +
- +
@@ -8857,7 +10084,7 @@ last received msg: %2$@
- +
diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/contents.json b/apps/ios/SimpleX Localizations/uk.xcloc/contents.json index 38238e7802..a93c702952 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/uk.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "uk", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file 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 8c3641549d..d5411f86e3 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 @@ -2,36 +2,9 @@
- +
- - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (可复制) @@ -109,6 +82,7 @@ %@ downloaded + %@ 已下载 No comment provided by engineer. @@ -126,8 +100,19 @@ %@ 已认证 No comment provided by engineer. + + %@ server + 服务器 + No comment provided by engineer. + + + %@ servers + 服务器 + No comment provided by engineer. + %@ uploaded + %@ 已上传 No comment provided by engineer. @@ -135,6 +120,11 @@ %@ 要连接! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ 和 %lld 成员 @@ -155,11 +145,36 @@ %d 天 time interval + + %d file(s) are still being downloaded. + 仍在下载 %d 个文件。 + forward confirmation reason + + + %d file(s) failed to download. + %d 个文件下载失败。 + forward confirmation reason + + + %d file(s) were deleted. + 已刪除 %d 个文件。 + forward confirmation reason + + + %d file(s) were not downloaded. + 未能下载 %d 个文件。 + forward confirmation reason + %d hours %d 小时 time interval + + %d messages not forwarded + 未转发 %d 条消息 + alert title + %d min %d 分钟 @@ -175,9 +190,14 @@ %d 秒 time interval + + %d seconds(s) + %d 秒 + delete after time + %d skipped message(s) - %d 跳过消息 + 跳过的 %d 条消息 integrity error chat item @@ -222,14 +242,17 @@ %lld messages blocked by admin + %lld 被管理员阻止的消息 No comment provided by engineer. %lld messages marked deleted + %lld 标记为已删除的消息 No comment provided by engineer. %lld messages moderated by %@ + %lld 审核的留言 by %@ No comment provided by engineer. @@ -242,11 +265,6 @@ %lld 种新的界面语言 No comment provided by engineer. - - %lld second(s) - %lld 秒 - No comment provided by engineer. - %lld seconds %lld 秒 @@ -297,49 +315,39 @@ 已跳过 %u 条消息。 No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) + (新) No comment provided by engineer. (this device v%@) + (此设备 v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **添加新联系人**:为您的联系人创建一次性二维码或者链接。 + + **Create 1-time link**: to create and share a new invitation link. + **添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接. No comment provided by engineer. **Create group**: to create a new group. + **创建群组**: 创建一个新群组. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **更私密**:每20分钟检查新消息。设备令牌和 SimpleX Chat 服务器共享,但是不会共享有您有多少联系人或者消息。 No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。 No comment provided by engineer. **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **请注意**: 在两台设备上使用相同的数据库将破坏来自您的连接的消息解密,作为一种安全保护. No comment provided by engineer. @@ -347,11 +355,16 @@ **请注意**:如果您丢失密码,您将无法恢复或者更改密码。 No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。 No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + **扫描/粘贴链接**:用您收到的链接连接。 + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **警告**:及时推送通知需要保存在钥匙串的密码。 @@ -359,6 +372,7 @@ **Warning**: the archive will be removed. + **警告**: 存档将被删除. No comment provided by engineer. @@ -376,11 +390,6 @@ \*加粗* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -417,13 +426,9 @@ - 编辑消息历史。 No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec + 0 秒 time to disappear @@ -434,7 +439,8 @@ 1 day 1天 - time interval + delete after time +time interval 1 hour @@ -449,12 +455,29 @@ 1 month 1月 - time interval + delete after time +time interval 1 week 1周 - time interval + delete after time +time interval + + + 1 year + 1 年 + delete after time + + + 1-time link + 一次性链接 + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + 一次性链接*只能给一名联系人*使用。当面或使用聊天应用分享链接。 + No comment provided by engineer. 5 minutes @@ -471,11 +494,6 @@ 30秒 No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -525,31 +543,32 @@ 中止地址更改? No comment provided by engineer. - - About SimpleX - 关于SimpleX - No comment provided by engineer. - About SimpleX Chat 关于SimpleX Chat No comment provided by engineer. - - About SimpleX address - 关于 SimpleX 地址 + + About operators + 关于运营方 No comment provided by engineer. Accent + 强调 No comment provided by engineer. Accept 接受 accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action + + + Accept conditions + 接受条款 + No comment provided by engineer. Accept connection request? @@ -565,18 +584,31 @@ Accept incognito 接受隐身聊天 accept contact request via notification - swipe action +swipe action + + + Accepted conditions + 已接受的条款 + No comment provided by engineer. Acknowledged + 确认 No comment provided by engineer. Acknowledgement errors + 确认错误 No comment provided by engineer. + + Active + 活跃 + token status text + Active connections + 活动连接 No comment provided by engineer. @@ -584,14 +616,14 @@ 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 No comment provided by engineer. - - Add contact - 添加联系人 + + Add friends + 添加好友 No comment provided by engineer. - - Add preset servers - 添加预设服务器 + + Add list + 添加列表 No comment provided by engineer. @@ -609,26 +641,54 @@ 扫描二维码来添加服务器。 No comment provided by engineer. + + Add team members + 添加团队成员 + No comment provided by engineer. + Add to another device 添加另一设备 No comment provided by engineer. + + Add to list + 添加到列表 + No comment provided by engineer. + Add welcome message 添加欢迎信息 No comment provided by engineer. + + Add your team members to the conversations. + 将你的团队成员加入对话。 + No comment provided by engineer. + + + Added media & file servers + 已添加媒体和文件服务器 + No comment provided by engineer. + + + Added message servers + 已添加消息服务器 + No comment provided by engineer. + Additional accent + 附加重音 No comment provided by engineer. Additional accent 2 + 附加重音 2 No comment provided by engineer. Additional secondary + 附加二级 No comment provided by engineer. @@ -641,6 +701,16 @@ 将中止地址更改。将使用旧接收地址。 No comment provided by engineer. + + Address or 1-time link? + 地址还是一次性链接? + No comment provided by engineer. + + + Address settings + 地址设置 + No comment provided by engineer. + Admins can block a member for all. 管理员可以为所有人封禁一名成员。 @@ -658,6 +728,12 @@ Advanced settings + 高级设置 + No comment provided by engineer. + + + All + 全部 No comment provided by engineer. @@ -670,13 +746,19 @@ 所有聊天记录和消息将被删除——这一行为无法撤销! No comment provided by engineer. + + All chats will be removed from the list %@, and the list deleted. + 列表 %@ 和其中全部聊天将被删除。 + alert message + All data is erased when it is entered. 所有数据在输入后将被删除。 No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. + 所有数据都是您设备的私有数据. No comment provided by engineer. @@ -684,6 +766,11 @@ 所有群组成员将保持连接。 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! 所有消息都将被删除 - 这无法被撤销! @@ -696,10 +783,22 @@ All new messages from %@ will be hidden! + 来自 %@ 的所有新消息都将被隐藏! No comment provided by engineer. All profiles + 所有配置文件 + profile dropdown + + + All reports will be archived for you. + 将为你存档所有举报。 + No comment provided by engineer. + + + All servers + 全部服务器 No comment provided by engineer. @@ -729,6 +828,7 @@ Allow calls? + 允许通话? No comment provided by engineer. @@ -738,11 +838,12 @@ Allow downgrade + 允许降级 No comment provided by engineer. Allow irreversible message deletion only if your contact allows it to you. (24 hours) - 仅有您的联系人许可后才允许不可撤回消息移除。 + 仅有您的联系人许可后才允许不可撤回消息移除 No comment provided by engineer. @@ -767,11 +868,17 @@ Allow sharing + 允许共享 No comment provided by engineer. Allow to irreversibly delete sent messages. (24 hours) - 允许不可撤回地删除已发送消息。 + 允许不可撤回地删除已发送消息 + No comment provided by engineer. + + + Allow to report messsages to moderators. + 允许向 moderators 举报消息。 No comment provided by engineer. @@ -811,7 +918,7 @@ Allow your contacts to irreversibly delete sent messages. (24 hours) - 允许您的联系人不可撤回地删除已发送消息。 + 允许您的联系人不可撤回地删除已发送消息 No comment provided by engineer. @@ -841,6 +948,7 @@ Always use private routing. + 始终使用私有路由。 No comment provided by engineer. @@ -853,11 +961,21 @@ 已创建一个包含所提供名字的空白聊天资料,应用程序照常打开。 No comment provided by engineer. + + Another reason + 另一个理由 + report reason + Answer call 接听来电 No comment provided by engineer. + + Anybody can host servers. + 任何人都可以托管服务器。 + No comment provided by engineer. + App build: %@ 应用程序构建:%@ @@ -873,6 +991,11 @@ 应用程序为新的本地文件(视频除外)加密。 No comment provided by engineer. + + App group: + 应用组: + No comment provided by engineer. + App icon 应用程序图标 @@ -888,6 +1011,11 @@ 应用程序密码被替换为自毁密码。 No comment provided by engineer. + + App session + 应用会话 + No comment provided by engineer. + App version 应用程序版本 @@ -910,6 +1038,22 @@ Apply to + 应用于 + No comment provided by engineer. + + + Archive + 存档 + No comment provided by engineer. + + + Archive %lld reports? + 存档 %lld 个举报? + No comment provided by engineer. + + + Archive all reports? + 存档所有举报? No comment provided by engineer. @@ -919,10 +1063,27 @@ Archive contacts to chat later. + 存档联系人以便稍后聊天. No comment provided by engineer. + + Archive report + 存档举报 + No comment provided by engineer. + + + Archive report? + 存档举报? + No comment provided by engineer. + + + Archive reports + 存档举报 + swipe action + Archived contacts + 已存档的联系人 No comment provided by engineer. @@ -990,6 +1151,11 @@ 自动接受图片 No comment provided by engineer. + + Auto-accept settings + 自动接受设置 + alert title + Back 返回 @@ -997,6 +1163,7 @@ Background + 背景 No comment provided by engineer. @@ -1014,11 +1181,26 @@ 错误消息散列 No comment provided by engineer. + + Better calls + 更佳的通话 + No comment provided by engineer. + Better groups 更佳的群组 No comment provided by engineer. + + Better groups performance + 更好的群性能 + No comment provided by engineer. + + + Better message dates. + 更好的消息日期。 + No comment provided by engineer. + Better messages 更好的消息 @@ -1026,10 +1208,32 @@ Better networking + 更好的网络 + No comment provided by engineer. + + + Better notifications + 更佳的通知 + No comment provided by engineer. + + + Better privacy and security + 更好的隐私和安全 + No comment provided by engineer. + + + Better security ✅ + 更佳的安全性✅ + No comment provided by engineer. + + + Better user experience + 更佳的使用体验 No comment provided by engineer. Black + 黑色 No comment provided by engineer. @@ -1069,10 +1273,12 @@ Blur for better privacy. + 模糊处理,提高私密性. No comment provided by engineer. Blur media + 模糊媒体 No comment provided by engineer. @@ -1082,7 +1288,7 @@ Both you and your contact can irreversibly delete sent messages. (24 hours) - 您和您的联系人都可以不可逆转地删除已发送的消息。 + 您和您的联系人都可以不可逆转地删除已发送的消息 No comment provided by engineer. @@ -1105,11 +1311,35 @@ 保加利亚语、芬兰语、泰语和乌克兰语——感谢用户和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 企业地址 + No comment provided by engineer. + + + Business chats + 企业聊天 + No comment provided by engineer. + + + Businesses + 企业 + 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: +- send only legal content in public groups. +- respect other users – no spam. + 使用 SimpleX Chat 代表您同意: +- 在公开群中只发送合法内容 +- 尊重其他用户 – 没有垃圾信息。 + No comment provided by engineer. + Call already ended! 通话已结束! @@ -1122,6 +1352,7 @@ Calls prohibited! + 禁止来电! No comment provided by engineer. @@ -1131,10 +1362,12 @@ Can't call contact + 无法呼叫联系人 No comment provided by engineer. Can't call member + 无法呼叫成员 No comment provided by engineer. @@ -1149,12 +1382,14 @@ Can't message member + 无法向成员发送消息 No comment provided by engineer. Cancel 取消 - No comment provided by engineer. + alert action +alert button Cancel migration @@ -1168,15 +1403,17 @@ Cannot forward message + 无法转发消息 No comment provided by engineer. Cannot receive file 无法接收文件 - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. + 超出容量-收件人未收到以前发送的邮件。 snd error text @@ -1189,6 +1426,16 @@ 更改 No comment provided by engineer. + + Change automatic message deletion? + 更改消息自动删除设置? + alert title + + + Change chat profiles + 更改聊天资料 + authentication reason + Change database passphrase? 更改数据库密码? @@ -1233,15 +1480,26 @@ Change self-destruct passcode 更改自毁密码 authentication reason - set passcode view +set passcode view - - Chat archive - 聊天档案 + + Chat + 聊天 + No comment provided by engineer. + + + Chat already exists + 聊天已存在 + No comment provided by engineer. + + + Chat already exists! + 聊天已存在! No comment provided by engineer. Chat colors + 聊天颜色 No comment provided by engineer. @@ -1261,6 +1519,7 @@ Chat database exported + 导出的聊天数据库 No comment provided by engineer. @@ -1285,6 +1544,7 @@ Chat list + 聊天列表 No comment provided by engineer. @@ -1297,8 +1557,29 @@ 聊天偏好设置 No comment provided by engineer. + + Chat preferences were changed. + 聊天偏好设置已修改。 + alert message + + + Chat profile + 用户资料 + No comment provided by engineer. + Chat theme + 聊天主题 + No comment provided by engineer. + + + 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. @@ -1306,10 +1587,20 @@ 聊天 No comment provided by engineer. + + Check messages every 20 min. + 每 20 分钟检查消息。 + No comment provided by engineer. + + + Check messages when allowed. + 在被允许时检查消息。 + No comment provided by engineer. + Check server address and try again. 检查服务器地址并再试一次。 - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1318,6 +1609,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + 在新设备上选择“从另一个设备迁移”并扫描二维码。 No comment provided by engineer. @@ -1332,14 +1624,17 @@ Chunks deleted + 已删除的块 No comment provided by engineer. Chunks downloaded + 下载的块 No comment provided by engineer. Chunks uploaded + 已下载的区块 No comment provided by engineer. @@ -1357,6 +1652,16 @@ 清除对话吗? No comment provided by engineer. + + Clear group? + 清除群? + No comment provided by engineer. + + + Clear or delete group? + 清除还是删除群? + No comment provided by engineer. + Clear private notes? 清除私密笔记? @@ -1369,12 +1674,19 @@ Color chats with the new themes. + 使用新主题为聊天着色。 No comment provided by engineer. Color mode + 颜色模式 No comment provided by engineer. + + Community guidelines violation + 违反社区指导方针 + report reason + Compare file 对比文件 @@ -1387,6 +1699,42 @@ Completed + 已完成 + No comment provided by engineer. + + + Conditions accepted on: %@. + 已于 %@ 接受条款。 + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + 已接受运营方 **%@** 的条款。 + No comment provided by engineer. + + + Conditions are already accepted for these operator(s): **%@**. + 已经接受下列运营方的条款:**%@**。 + No comment provided by engineer. + + + Conditions of use + 使用条款 + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + 将接受下列运营方的条款:**%@**。 + No comment provided by engineer. + + + Conditions will be accepted on: %@. + 将于 %@ 接受条款。 + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + 将在 %@ 自动接受启用的运营方的条款。 No comment provided by engineer. @@ -1394,8 +1742,9 @@ 配置 ICE 服务器 No comment provided by engineer. - - Configured %@ servers + + Configure server operators + 配置服务器运营方 No comment provided by engineer. @@ -1410,6 +1759,7 @@ Confirm contact deletion? + 确认删除联系人? No comment provided by engineer. @@ -1419,6 +1769,7 @@ Confirm files from unknown servers. + 确认来自未知服务器的文件。 No comment provided by engineer. @@ -1446,6 +1797,11 @@ 确认上传 No comment provided by engineer. + + Confirmed + 已确定 + token status text + Connect 连接 @@ -1468,6 +1824,7 @@ Connect to your friends faster. + 更快地与您的朋友联系。 No comment provided by engineer. @@ -1478,15 +1835,20 @@ Connect to yourself? This is your own SimpleX address! + 与自己建立联系? +这是您自己的 SimpleX 地址! No comment provided by engineer. Connect to yourself? This is your own one-time link! + 与自己建立联系? +这是您自己的一次性链接! No comment provided by engineer. Connect via contact address + 通过联系地址连接 No comment provided by engineer. @@ -1501,10 +1863,12 @@ This is your own one-time link! Connect with %@ + 与 %@连接 No comment provided by engineer. Connected + 已连接 No comment provided by engineer. @@ -1514,6 +1878,7 @@ This is your own one-time link! Connected servers + 已连接的服务器 No comment provided by engineer. @@ -1523,6 +1888,7 @@ This is your own one-time link! Connecting + 正在连接 No comment provided by engineer. @@ -1537,6 +1903,7 @@ This is your own one-time link! Connecting to contact, please wait or check later! + 正在连接到联系人,请稍候或稍后检查! No comment provided by engineer. @@ -1551,6 +1918,12 @@ This is your own one-time link! Connection and servers status. + 连接和服务器状态。 + No comment provided by engineer. + + + Connection blocked + 连接被阻止 No comment provided by engineer. @@ -1563,8 +1936,20 @@ This is your own one-time link! 连接错误(AUTH) No comment provided by engineer. + + Connection is blocked by server operator: +%@ + 连接被运营方 %@ 阻止 + No comment provided by engineer. + + + Connection not ready. + 连接未就绪。 + No comment provided by engineer. + Connection notifications + 连接通知 No comment provided by engineer. @@ -1572,6 +1957,16 @@ This is your own one-time link! 已发送连接请求! No comment provided by engineer. + + Connection requires encryption renegotiation. + 连接需要加密重协商。 + No comment provided by engineer. + + + Connection security + 连接安全性 + No comment provided by engineer. + Connection terminated 连接被终止 @@ -1584,10 +1979,12 @@ This is your own one-time link! Connection with desktop stopped + 与桌面的连接已停止 No comment provided by engineer. Connections + 连接 No comment provided by engineer. @@ -1602,6 +1999,7 @@ This is your own one-time link! Contact deleted! + 联系人已删除! No comment provided by engineer. @@ -1616,6 +2014,7 @@ This is your own one-time link! Contact is deleted. + 联系人被删除。 No comment provided by engineer. @@ -1630,6 +2029,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! + 联系人将被删除-这是无法撤消的! No comment provided by engineer. @@ -1642,6 +2042,11 @@ This is your own one-time link! 联系人可以将信息标记为删除;您将可以查看这些信息。 No comment provided by engineer. + + Content violates conditions of use + 内容违反使用条款 + blocking reason + Continue 继续 @@ -1649,6 +2054,7 @@ This is your own one-time link! Conversation deleted! + 对话已删除! No comment provided by engineer. @@ -1658,6 +2064,7 @@ This is your own one-time link! Copy error + 复制错误 No comment provided by engineer. @@ -1665,8 +2072,14 @@ This is your own one-time link! 核心版本: v%@ No comment provided by engineer. + + Corner + 拐角 + No comment provided by engineer. + Correct name to %@? + 将名称更正为 %@? No comment provided by engineer. @@ -1674,6 +2087,11 @@ This is your own one-time link! 创建 No comment provided by engineer. + + Create 1-time link + 创建一次性链接 + No comment provided by engineer. + Create SimpleX address 创建 SimpleX 地址 @@ -1681,12 +2099,7 @@ This is your own one-time link! Create a group using a random profile. - 使用随机身份创建群组 - No comment provided by engineer. - - - Create an address to let people connect with you. - 创建一个地址,让人们与您联系。 + 使用随机身份创建群组. No comment provided by engineer. @@ -1709,6 +2122,11 @@ This is your own one-time link! 创建链接 No comment provided by engineer. + + Create list + 创建列表 + No comment provided by engineer. + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 在[桌面应用程序](https://simplex.chat/downloads/)中创建新的个人资料。 💻 @@ -1736,6 +2154,7 @@ This is your own one-time link! Created + 已创建 No comment provided by engineer. @@ -1745,13 +2164,9 @@ This is your own one-time link! Created at: %@ + 创建于:%@ copied message info - - Created on %@ - 创建于 %@ - No comment provided by engineer. - Creating archive link 正在创建存档链接 @@ -1767,6 +2182,11 @@ This is your own one-time link! 当前密码 No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + 无法加载当前条款文本,你可以通过此链接审阅条款: + No comment provided by engineer. + Current passphrase… 现有密码…… @@ -1774,6 +2194,7 @@ This is your own one-time link! Current profile + 当前配置文件 No comment provided by engineer. @@ -1786,8 +2207,14 @@ This is your own one-time link! 自定义时间 No comment provided by engineer. + + Customizable message shape. + 可自定义消息形状。 + No comment provided by engineer. + Customize theme + 自定义主题 No comment provided by engineer. @@ -1797,6 +2224,7 @@ This is your own one-time link! Dark mode colors + 深色模式颜色 No comment provided by engineer. @@ -1899,6 +2327,7 @@ This is your own one-time link! Debug delivery + 调试交付 No comment provided by engineer. @@ -1914,15 +2343,17 @@ This is your own one-time link! Delete 删除 - chat item action - swipe action + alert action +swipe action Delete %lld messages of members? + 删除成员的 %lld 消息? No comment provided by engineer. Delete %lld messages? + 删除 %lld 消息? No comment provided by engineer. @@ -1950,14 +2381,14 @@ This is your own one-time link! 删除并通知联系人 No comment provided by engineer. - - Delete archive - 删除档案 + + Delete chat + 删除聊天 No comment provided by engineer. - - Delete chat archive? - 删除聊天档案? + + Delete chat messages from your device. + 从你的设备删除聊天消息。 No comment provided by engineer. @@ -1970,6 +2401,11 @@ This is your own one-time link! 删除聊天资料? No comment provided by engineer. + + Delete chat? + 删除聊天? + No comment provided by engineer. + Delete connection 删除连接 @@ -1982,6 +2418,7 @@ This is your own one-time link! Delete contact? + 删除联系人? No comment provided by engineer. @@ -2044,6 +2481,11 @@ This is your own one-time link! 删除链接? No comment provided by engineer. + + Delete list? + 删除列表? + alert title + Delete member message? 删除成员消息? @@ -2057,7 +2499,7 @@ This is your own one-time link! Delete messages 删除消息 - No comment provided by engineer. + alert button Delete messages after @@ -2074,6 +2516,11 @@ This is your own one-time link! 删除旧数据库吗? No comment provided by engineer. + + Delete or moderate up to 200 messages. + 允许自行删除或管理员移除最多200条消息。 + No comment provided by engineer. + Delete pending connection? 删除待定连接? @@ -2089,8 +2536,14 @@ This is your own one-time link! 删除队列 server test step + + Delete report + 删除举报 + No comment provided by engineer. + Delete up to 20 messages at once. + 一次最多删除 20 条信息。 No comment provided by engineer. @@ -2100,10 +2553,12 @@ This is your own one-time link! Delete without notification + 删除而不通知 No comment provided by engineer. Deleted + 已删除 No comment provided by engineer. @@ -2118,6 +2573,12 @@ This is your own one-time link! Deletion errors + 删除错误 + No comment provided by engineer. + + + Delivered even when Apple drops them. + 已送达,即使苹果已将其删除。 No comment provided by engineer. @@ -2147,6 +2608,7 @@ This is your own one-time link! Desktop app version %@ is not compatible with this app. + 桌面应用程序版本 %@ 与此应用程序不兼容。 No comment provided by engineer. @@ -2156,22 +2618,27 @@ This is your own one-time link! Destination server address of %@ is incompatible with forwarding server %@ settings. + 目标服务器地址 %@ 与转发服务器 %@ 设置不兼容。 No comment provided by engineer. Destination server error: %@ + 目标服务器错误:%@ snd error text Destination server version of %@ is incompatible with forwarding server %@. + 目标服务器版本 %@ 与转发服务器 %@ 不兼容。 No comment provided by engineer. Detailed statistics + 详细的统计数据 No comment provided by engineer. Details + 详细信息 No comment provided by engineer. @@ -2181,6 +2648,7 @@ This is your own one-time link! Developer options + 开发者选项 No comment provided by engineer. @@ -2195,7 +2663,7 @@ This is your own one-time link! Device authentication is disabled. Turning off SimpleX Lock. - 设备验证被禁用。关闭 SimpleX 锁定。 + 设备验证已禁用。 SimpleX 已解锁。 No comment provided by engineer. @@ -2213,9 +2681,14 @@ This is your own one-time link! 私信 chat feature - - Direct messages between members are prohibited in this group. - 此群中禁止成员之间私信。 + + 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. @@ -2228,6 +2701,16 @@ This is your own one-time link! 禁用 SimpleX 锁定 authentication reason + + Disable automatic message deletion? + 禁用消息自动销毁? + alert title + + + Disable delete messages + 停用消息删除 + alert button + Disable for all 全部禁用 @@ -2235,6 +2718,7 @@ This is your own one-time link! Disabled + 禁用 No comment provided by engineer. @@ -2252,8 +2736,8 @@ This is your own one-time link! 此聊天中禁止显示限时消息。 No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. 该组禁止限时消息。 No comment provided by engineer. @@ -2289,6 +2773,7 @@ This is your own one-time link! Do NOT send messages directly, even if your or destination server does not support private routing. + 请勿直接发送消息,即使您的服务器或目标服务器不支持私有路由。 No comment provided by engineer. @@ -2298,6 +2783,7 @@ This is your own one-time link! Do NOT use private routing. + 不要使用私有路由。 No comment provided by engineer. @@ -2310,6 +2796,16 @@ This is your own one-time link! 不给新成员发送历史消息。 No comment provided by engineer. + + Do not use credentials with proxy. + 代理不使用身份验证凭据。 + No comment provided by engineer. + + + Documents: + 文档: + No comment provided by engineer. + Don't create address 不创建地址 @@ -2320,11 +2816,21 @@ This is your own one-time link! 不要启用 No comment provided by engineer. + + Don't miss important messages. + 不错过重要消息。 + No comment provided by engineer. + Don't show again 不再显示 No comment provided by engineer. + + Done + 完成 + No comment provided by engineer. + Downgrade and open chat 降级并打开聊天 @@ -2333,10 +2839,12 @@ This is your own one-time link! Download 下载 - chat item action + alert button +chat item action Download errors + 下载错误 No comment provided by engineer. @@ -2349,12 +2857,19 @@ This is your own one-time link! 下载文件 server test step + + Download files + 下载文件 + alert action + Downloaded + 已下载 No comment provided by engineer. Downloaded files + 下载的文件 No comment provided by engineer. @@ -2377,6 +2892,11 @@ This is your own one-time link! 时长 No comment provided by engineer. + + E2E encrypted notifications. + 端到端加密的通知。 + No comment provided by engineer. + Edit 编辑 @@ -2397,6 +2917,11 @@ This is your own one-time link! 启用(保持覆盖) No comment provided by engineer. + + Enable Flux in Network & servers settings for better metadata privacy. + 在“网络&服务器”设置中启用 Flux,更好地保护元数据隐私。 + No comment provided by engineer. + Enable SimpleX Lock 启用 SimpleX 锁定 @@ -2410,7 +2935,7 @@ This is your own one-time link! Enable automatic message deletion? 启用自动删除消息? - No comment provided by engineer. + alert title Enable camera access @@ -2459,6 +2984,7 @@ This is your own one-time link! Enabled + 已启用 No comment provided by engineer. @@ -2498,6 +3024,7 @@ This is your own one-time link! Encrypted message: app is stopped + 加密消息:应用程序已停止 notification @@ -2535,6 +3062,11 @@ This is your own one-time link! 加密重协商失败了。 No comment provided by engineer. + + Encryption renegotiation in progress. + 正进行加密重协商。 + No comment provided by engineer. + Enter Passcode 输入密码 @@ -2547,6 +3079,7 @@ This is your own one-time link! Enter group name… + 输入组名称… No comment provided by engineer. @@ -2586,6 +3119,7 @@ This is your own one-time link! Enter your name… + 请输入您的姓名… No comment provided by engineer. @@ -2598,26 +3132,36 @@ This is your own one-time link! 中止地址更改错误 No comment provided by engineer. + + Error accepting conditions + 接受条款出错 + alert title + Error accepting contact request 接受联系人请求错误 No comment provided by engineer. - - Error accessing database file - 访问数据库文件错误 - No comment provided by engineer. - Error adding member(s) 添加成员错误 No comment provided by engineer. + + Error adding server + 添加服务器出错 + alert title + Error changing address 更改地址错误 No comment provided by engineer. + + Error changing connection profile + 更改连接资料出错 + No comment provided by engineer. + Error changing role 更改角色错误 @@ -2628,8 +3172,18 @@ This is your own one-time link! 更改设置错误 No comment provided by engineer. + + Error changing to incognito! + 切换至隐身聊天出错! + No comment provided by engineer. + + + Error checking token status + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. + 连接到转发服务器 %@ 时出错。请稍后尝试。 No comment provided by engineer. @@ -2647,6 +3201,11 @@ This is your own one-time link! 创建群组链接错误 No comment provided by engineer. + + Error creating list + 创建列表出错 + alert title + Error creating member contact 创建成员联系人时出错 @@ -2662,6 +3221,11 @@ This is your own one-time link! 创建资料错误! No comment provided by engineer. + + Error creating report + 创建举报出错 + No comment provided by engineer. + Error decrypting file 解密文件时出错 @@ -2729,6 +3293,7 @@ This is your own one-time link! Error exporting theme: %@ + 导出主题时出错: %@ No comment provided by engineer. @@ -2741,40 +3306,54 @@ This is your own one-time link! 加入群组错误 No comment provided by engineer. - - Error loading %@ servers - 加载 %@ 服务器错误 + + Error loading servers + 加载服务器出错 + alert title + + + Error migrating settings + 迁移设置出错 No comment provided by engineer. Error opening chat + 打开聊天时出错 No comment provided by engineer. Error receiving file 接收文件错误 - No comment provided by engineer. + alert title Error reconnecting server + 重新连接服务器时出错 No comment provided by engineer. Error reconnecting servers + 重新连接服务器时出错 No comment provided by engineer. + + Error registering for notifications + 注册消息推送出错 + alert title + Error removing member 删除成员错误 No comment provided by engineer. + + Error reordering lists + 重排列表出错 + alert title + Error resetting statistics - No comment provided by engineer. - - - Error saving %@ servers - 保存 %@ 服务器错误 + 重置统计信息时出错 No comment provided by engineer. @@ -2782,6 +3361,11 @@ This is your own one-time link! 保存 ICE 服务器错误 No comment provided by engineer. + + Error saving chat list + 保存聊天列表出错 + alert title + Error saving group profile 保存群组资料错误 @@ -2797,6 +3381,11 @@ This is your own one-time link! 保存密码到钥匙串错误 No comment provided by engineer. + + Error saving servers + 保存服务器出错 + alert title + Error saving settings 保存设置出错 @@ -2809,6 +3398,7 @@ This is your own one-time link! Error scanning code: %@ + 扫描代码时出错:%@ No comment provided by engineer. @@ -2841,16 +3431,26 @@ This is your own one-time link! 停止聊天错误 No comment provided by engineer. + + Error switching profile + 切换配置文件出错 + No comment provided by engineer. + Error switching profile! 切换资料错误! - No comment provided by engineer. + alertTitle Error synchronizing connection 同步连接错误 No comment provided by engineer. + + Error testing server connection + 检验服务器连接出错 + No comment provided by engineer. + Error updating group link 更新群组链接错误 @@ -2861,6 +3461,11 @@ This is your own one-time link! 更新消息错误 No comment provided by engineer. + + Error updating server + 更新服务器出错 + alert title + Error updating settings 更新设置错误 @@ -2889,8 +3494,9 @@ This is your own one-time link! Error: %@ 错误: %@ - file error text - snd error text + alert message +file error text +snd error text Error: URL is invalid @@ -2904,8 +3510,14 @@ This is your own one-time link! Errors + 错误 No comment provided by engineer. + + Errors in servers configuration. + 服务器配置有错误。 + servers error + Even when disabled in the conversation. 即使在对话中被禁用。 @@ -2921,6 +3533,11 @@ This is your own one-time link! 展开 chat item action + + Expired + 已过期 + token status text + Export database 导出数据库 @@ -2933,6 +3550,7 @@ This is your own one-time link! Export theme + 导出主题 No comment provided by engineer. @@ -2960,34 +3578,68 @@ This is your own one-time link! 快速且无需等待发件人在线! No comment provided by engineer. + + Faster deletion of groups. + 更快地删除群。 + No comment provided by engineer. + Faster joining and more reliable messages. 加入速度更快、信息更可靠。 No comment provided by engineer. + + Faster sending messages. + 更快发送消息。 + No comment provided by engineer. + Favorite 最喜欢 swipe action + + Favorites + 收藏 + No comment provided by engineer. + File error - No comment provided by engineer. + 文件错误 + file error alert title + + + File errors: +%@ + 文件错误: +%@ + alert message + + + File is blocked by server operator: +%@. + 文件被服务器运营方阻止: +%@。 + file error text File not found - most likely file was deleted or cancelled. + 找不到文件 - 很可能文件已被删除或取消。 file error text File server error: %@ + 文件服务器错误:%@ file error text File status + 文件状态 No comment provided by engineer. File status: %@ + 文件状态:%@ copied message info @@ -3012,6 +3664,7 @@ This is your own one-time link! Files + 文件 No comment provided by engineer. @@ -3024,8 +3677,8 @@ This is your own one-time link! 文件和媒体 chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. 此群组中禁止文件和媒体。 No comment provided by engineer. @@ -3051,7 +3704,7 @@ This is your own one-time link! Finalize migration on another device. - 在另一部设备上完成迁移 + 在另一部设备上完成迁移. No comment provided by engineer. @@ -3094,21 +3747,71 @@ This is your own one-time link! 修复群组成员不支持的问题 No comment provided by engineer. + + For all moderators + 所有 moderators + No comment provided by engineer. + + + For chat profile %@: + 为聊天资料 %@: + servers error + For console 用于控制台 No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + 比如,如果你通过 SimpleX 服务器收到消息,应用会通过 Flux 服务器传送它们。 + No comment provided by engineer. + + + For me + 仅自己 + No comment provided by engineer. + + + For private routing + 用于私密路由 + No comment provided by engineer. + + + For social media + 用于社交媒体 + No comment provided by engineer. + Forward 转发 chat item action + + Forward %d message(s)? + 转发 %d 条消息? + alert title + Forward and save messages 转发并保存消息 No comment provided by engineer. + + Forward messages + 已转发的消息 + alert action + + + Forward messages without files? + 仅转发消息不转发文件? + alert message + + + Forward up to 20 messages at once. + 一次转发最多20条消息。 + No comment provided by engineer. + Forwarded 已转发 @@ -3119,26 +3822,38 @@ This is your own one-time link! 转发自 No comment provided by engineer. + + Forwarding %lld messages + 正在转发 %lld 条消息 + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. + 转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。 No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + 转发服务器地址与网络设置不兼容:%@。 No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + 转发服务器版本与网络设置不兼容:%@。 No comment provided by engineer. Forwarding server: %1$@ Destination server error: %2$@ + 转发服务器: %1$@ +目标服务器错误: %2$@ snd error text Forwarding server: %1$@ Error: %2$@ + 转发服务器: %1$@ +错误: %2$@ snd error text @@ -3161,11 +3876,6 @@ Error: %2$@ 全名(可选) No comment provided by engineer. - - Full name: - 全名: - No comment provided by engineer. - Fully decentralized – visible only to members. 完全去中心化 - 仅对成员可见。 @@ -3186,12 +3896,19 @@ Error: %2$@ GIF 和贴纸 No comment provided by engineer. + + Get notified when mentioned. + 被提及时收到通知。 + No comment provided by engineer. + Good afternoon! + 下午好! message preview Good morning! + 早上好! message preview @@ -3201,6 +3918,7 @@ Error: %2$@ Group already exists + 群组已存在 No comment provided by engineer. @@ -3248,41 +3966,6 @@ Error: %2$@ 群组链接 No comment provided by engineer. - - Group members can add message reactions. - 群组成员可以添加信息回应。 - No comment provided by engineer. - - - Group members can irreversibly delete sent messages. (24 hours) - 群组成员可以不可撤回地删除已发送的消息。 - No comment provided by engineer. - - - Group members can send SimpleX links. - 群成员可发送 SimpleX 链接。 - No comment provided by engineer. - - - Group members can send direct messages. - 群组成员可以私信。 - No comment provided by engineer. - - - Group members can send disappearing messages. - 群组成员可以发送限时消息。 - No comment provided by engineer. - - - Group members can send files and media. - 群组成员可以发送文件和媒体。 - No comment provided by engineer. - - - Group members can send voice messages. - 群组成员可以发送语音消息。 - No comment provided by engineer. - Group message: 群组消息: @@ -3323,11 +4006,21 @@ Error: %2$@ 将为您删除群组——此操作无法撤消! No comment provided by engineer. + + Groups + + No comment provided by engineer. + Help 帮助 No comment provided by engineer. + + Help admins moderating their groups. + 帮助管理员管理群组。 + No comment provided by engineer. + Hidden 隐藏 @@ -3378,10 +4071,20 @@ Error: %2$@ SimpleX的工作原理 No comment provided by engineer. + + How it affects privacy + 它如何影响隐私 + No comment provided by engineer. + + + How it helps privacy + 它如何帮助隐私 + No comment provided by engineer. + How it works 工作原理 - No comment provided by engineer. + alert button How to @@ -3400,6 +4103,7 @@ Error: %2$@ Hungarian interface + 匈牙利语界面 No comment provided by engineer. @@ -3407,6 +4111,11 @@ Error: %2$@ ICE 服务器(每行一个) No comment provided by engineer. + + IP address + IP 地址 + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. 如果您不能亲自见面,可以在视频通话中展示二维码,或分享链接。 @@ -3447,8 +4156,8 @@ Error: %2$@ 立即 No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam 不受垃圾和骚扰消息影响 No comment provided by engineer. @@ -3474,6 +4183,7 @@ Error: %2$@ Import theme + 导入主题 No comment provided by engineer. @@ -3481,6 +4191,13 @@ Error: %2$@ 正在导入存档 No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + 改善传送,降低流量使用。 +更多改进即将推出! + No comment provided by engineer. + Improved message delivery 改进了消息传递 @@ -3511,6 +4228,16 @@ Error: %2$@ 通话声音 No comment provided by engineer. + + Inappropriate content + 不当内容 + report reason + + + Inappropriate profile + 不当个人资料 + report reason + Incognito 隐身聊天 @@ -3581,6 +4308,11 @@ Error: %2$@ 安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + 即时 + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3588,11 +4320,6 @@ Error: %2$@ No comment provided by engineer. - - Instantly - 即时 - No comment provided by engineer. - Interface 界面 @@ -3600,8 +4327,34 @@ Error: %2$@ Interface colors + 界面颜色 No comment provided by engineer. + + Invalid + 无效 + token status text + + + Invalid (bad token) + Token 无效 + token status text + + + Invalid (expired) + 无效(已过期) + token status text + + + Invalid (unregistered) + 无效(未注册) + token status text + + + Invalid (wrong topic) + 无效(话题有误) + token status text + Invalid QR code 无效的二维码 @@ -3634,12 +4387,13 @@ Error: %2$@ Invalid response + 无效的响应 No comment provided by engineer. Invalid server address! 无效的服务器地址! - No comment provided by engineer. + alert title Invalid status @@ -3661,6 +4415,11 @@ Error: %2$@ 邀请成员 No comment provided by engineer. + + Invite to chat + 邀请加入聊天 + No comment provided by engineer. + Invite to group 邀请加入群组 @@ -3676,8 +4435,8 @@ Error: %2$@ 此聊天中禁止不可撤回消息移除。 No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. 此群组中禁止不可撤回消息移除。 No comment provided by engineer. @@ -3704,6 +4463,7 @@ Error: %2$@ It protects your IP address and connections. + 它可以保护您的 IP 地址和连接。 No comment provided by engineer. @@ -3748,11 +4508,14 @@ Error: %2$@ Join with current profile + 使用当前档案加入 No comment provided by engineer. Join your group? This is your link for group %@! + 加入您的群组? +这是您组 %@ 的链接! No comment provided by engineer. @@ -3763,20 +4526,22 @@ This is your link for group %@! Keep 保留 - No comment provided by engineer. + alert action Keep conversation + 保持对话 No comment provided by engineer. Keep the app open to use it from desktop + 保持应用程序打开状态以从桌面使用它 No comment provided by engineer. Keep unused invitation? 保留未使用的邀请吗? - No comment provided by engineer. + alert title Keep your connections @@ -3813,6 +4578,16 @@ This is your link for group %@! 离开 swipe action + + Leave chat + 离开聊天 + No comment provided by engineer. + + + Leave chat? + 离开聊天? + No comment provided by engineer. + Leave group 离开群组 @@ -3853,6 +4628,21 @@ This is your link for group %@! 已链接桌面 No comment provided by engineer. + + List + 列表 + swipe action + + + List name and emoji should be different for all lists. + 所有列表的名称和表情符号都应不同。 + No comment provided by engineer. + + + List name... + 列表名… + No comment provided by engineer. + Live message! 实时消息! @@ -3863,11 +4653,6 @@ This is your link for group %@! 实时消息 No comment provided by engineer. - - Local - 本地 - No comment provided by engineer. - Local name 本地名称 @@ -3888,11 +4673,6 @@ This is your link for group %@! 锁定模式 No comment provided by engineer. - - Make a private connection - 建立私密连接 - No comment provided by engineer. - Make one message disappear 使一条消息消失 @@ -3903,21 +4683,11 @@ This is your link for group %@! 将个人资料设为私密! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - 请确保 %@服 务器地址格式正确,每行一个地址并且不重复 (%@)。 - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. 确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。 No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - 许多人问: *如果SimpleX没有用户标识符,它怎么传递信息?* - No comment provided by engineer. - Mark deleted for everyone 标记为所有人已删除 @@ -3945,10 +4715,12 @@ This is your link for group %@! Media & file servers + Media & file servers No comment provided by engineer. Medium + 中等 blur media @@ -3958,8 +4730,19 @@ This is your link for group %@! Member inactive + 成员不活跃 item status text + + Member reports + 成员举报 + chat feature + + + Member role will be changed to "%@". All chat members will be notified. + 将变更成员角色为“%@”。所有成员都会收到通知。 + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. 成员角色将更改为 "%@"。所有群成员将收到通知。 @@ -3970,13 +4753,64 @@ This is your link for group %@! 成员角色将更改为 "%@"。该成员将收到一份新的邀请。 No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + 将从聊天中删除成员 - 此操作无法撤销! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! 成员将被移出群组——此操作无法撤消! No comment provided by engineer. + + Members can add message reactions. + 群组成员可以添加信息回应。 + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + 群组成员可以不可撤回地删除已发送的消息 + No comment provided by engineer. + + + Members can report messsages to moderators. + 成员可以向 moderators 举报消息。 + No comment provided by engineer. + + + Members can send SimpleX links. + 群成员可发送 SimpleX 链接。 + No comment provided by engineer. + + + Members can send direct messages. + 群组成员可以私信。 + No comment provided by engineer. + + + Members can send disappearing messages. + 群组成员可以发送限时消息。 + No comment provided by engineer. + + + Members can send files and media. + 群组成员可以发送文件和媒体。 + No comment provided by engineer. + + + Members can send voice messages. + 群组成员可以发送语音消息。 + No comment provided by engineer. + + + Mention members 👋 + 提及成员👋 + No comment provided by engineer. + Menus + 菜单 No comment provided by engineer. @@ -3991,6 +4825,7 @@ This is your link for group %@! Message delivery warning + 消息传递警告 item status text @@ -4000,14 +4835,17 @@ This is your link for group %@! Message forwarded + 消息已转发 item status text Message may be delivered later if member becomes active. + 如果 member 变为活动状态,则稍后可能会发送消息。 item status description Message queue info + 消息队列信息 No comment provided by engineer. @@ -4020,17 +4858,24 @@ This is your link for group %@! 该聊天禁用了消息回应。 No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. 该群组禁用了消息回应。 No comment provided by engineer. Message reception + 消息接收 No comment provided by engineer. Message servers + 消息服务器 + No comment provided by engineer. + + + Message shape + 消息形状 No comment provided by engineer. @@ -4040,10 +4885,12 @@ This is your link for group %@! Message status + 消息状态 No comment provided by engineer. Message status: %@ + 消息状态:%@ copied message info @@ -4068,22 +4915,37 @@ This is your link for group %@! Messages from %@ will be shown! + 将显示来自 %@ 的消息! No comment provided by engineer. + + Messages in this chat will never be deleted. + 此聊天中的消息永远不会被删除。 + alert message + Messages received + 收到的消息 No comment provided by engineer. Messages sent + 已发送的消息 No comment provided by engineer. + + Messages were deleted after you selected them. + 在你选中消息后这些消息已被删除。 + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + 消息、文件和通话受到 **端到端加密** 的保护,具有完全正向保密、否认和闯入恢复。 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. @@ -4141,9 +5003,9 @@ This is your link for group %@! 迁移完成 No comment provided by engineer. - - Migrations: %@ - 迁移:%@ + + Migrations: + 迁移 No comment provided by engineer. @@ -4161,6 +5023,11 @@ This is your link for group %@! 已被管理员移除于:%@ copied message info + + More + 更多 + swipe action + More improvements are coming soon! 更多改进即将推出! @@ -4171,6 +5038,11 @@ This is your link for group %@! 更可靠的网络连接。 No comment provided by engineer. + + More reliable notifications + 更可靠的通知 + No comment provided by engineer. + Most likely this connection is deleted. 此连接很可能已被删除。 @@ -4184,7 +5056,12 @@ This is your link for group %@! Mute 静音 - swipe action + notification label action + + + Mute all + 全部静音 + notification label action Muted when inactive! @@ -4206,8 +5083,14 @@ This is your link for group %@! 网络连接 No comment provided by engineer. + + Network decentralization + 网络去中心化 + No comment provided by engineer. + Network issues - message expired after many attempts to send it. + 网络问题 - 消息在多次尝试发送后过期。 snd error text @@ -4215,6 +5098,11 @@ This is your link for group %@! 网络管理 No comment provided by engineer. + + Network operator + 网络运营方 + No comment provided by engineer. + Network settings 网络设置 @@ -4225,11 +5113,26 @@ This is your link for group %@! 网络状态 No comment provided by engineer. + + New + + token status text + New Passcode 新密码 No comment provided by engineer. + + 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 凭据。 + No comment provided by engineer. + New chat 新聊天 @@ -4237,6 +5140,7 @@ This is your link for group %@! New chat experience 🎉 + 新的聊天体验 🎉 No comment provided by engineer. @@ -4249,11 +5153,6 @@ This is your link for group %@! 新联系人: notification - - New database archive - 新数据库存档 - No comment provided by engineer. - New desktop app! 全新桌面应用! @@ -4264,6 +5163,11 @@ This is your link for group %@! 新显示名 No comment provided by engineer. + + New events + 新事件 + notification + New in %@ %@ 的新内容 @@ -4271,6 +5175,7 @@ This is your link for group %@! New media options + 新媒体选项 No comment provided by engineer. @@ -4288,6 +5193,11 @@ This is your link for group %@! 新密码…… No comment provided by engineer. + + New server + 新服务器 + No comment provided by engineer. + No @@ -4298,6 +5208,21 @@ This is your link for group %@! 没有应用程序密码 Authentication unavailable + + No chats + 无聊天 + No comment provided by engineer. + + + No chats found + 找不到聊天 + No comment provided by engineer. + + + No chats in list %@ + 列表 %@ 中无聊天 + No comment provided by engineer. + No contacts selected 未选择联系人 @@ -4320,6 +5245,7 @@ This is your link for group %@! No direct connection yet, message is forwarded by admin. + 还没有直接连接,消息由管理员转发。 item status description @@ -4339,32 +5265,109 @@ This is your link for group %@! No info, try to reload + 无信息,尝试重新加载 No comment provided by engineer. + + No media & file servers. + 无媒体和文件服务器。 + servers error + + + No message + 无消息 + No comment provided by engineer. + + + No message servers. + 无消息服务器。 + servers error + No network connection 无网络连接 No comment provided by engineer. + + No permission to record speech + 无录音权限 + No comment provided by engineer. + + + No permission to record video + 无录像权限 + No comment provided by engineer. + No permission to record voice message 没有录制语音消息的权限 No comment provided by engineer. + + No push server + 本地 + No comment provided by engineer. + No received or sent files 未收到或发送文件 No comment provided by engineer. + + No servers for private message routing. + 无私密消息路由服务器。 + servers error + + + No servers to receive files. + 无文件接收服务器。 + servers error + + + No servers to receive messages. + 无消息接收服务器。 + servers error + + + No servers to send files. + 无文件发送服务器。 + servers error + + + No token! + 无 token! + alert title + + + No unread chats + 没有未读聊天 + No comment provided by engineer. + + + No user identifiers. + 没有用户标识符。 + No comment provided by engineer. + Not compatible! 不兼容! No comment provided by engineer. + + Notes + 附注 + No comment provided by engineer. + Nothing selected + 未选中任何内容 No comment provided by engineer. + + Nothing to forward! + 无可转发! + alert title + Notifications 通知 @@ -4375,6 +5378,21 @@ This is your link for group %@! 通知被禁用! No comment provided by engineer. + + Notifications error + 通知错误 + alert title + + + Notifications privacy + 通知隐私 + No comment provided by engineer. + + + Notifications status + 通知状态 + alert title + Now admins can: - delete members' messages. @@ -4397,18 +5415,13 @@ This is your link for group %@! Ok 好的 - No comment provided by engineer. + alert button Old database 旧的数据库 No comment provided by engineer. - - Old database archive - 旧数据库存档 - No comment provided by engineer. - One-time invitation link 一次性邀请链接 @@ -4417,13 +5430,15 @@ This is your link for group %@! Onion hosts will be **required** for connection. Requires compatible VPN. - Onion 主机将用于连接。需要启用 VPN。 + Onion 主机将是连接所必需的。 +需要兼容的 VPN。 No comment provided by engineer. Onion hosts will be used when available. Requires compatible VPN. - 当可用时,将使用 Onion 主机。需要启用 VPN。 + 如果可用,将使用洋葱主机。 +需要兼容的 VPN。 No comment provided by engineer. @@ -4431,13 +5446,19 @@ Requires compatible VPN. 将不会使用 Onion 主机。 No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only chat owners can change preferences. + 仅聊天所有人可更改首选项。 + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages. 只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。 No comment provided by engineer. Only delete conversation + 仅删除对话 No comment provided by engineer. @@ -4455,6 +5476,16 @@ Requires compatible VPN. 只有群主可以启用语音信息。 No comment provided by engineer. + + Only sender and moderators see it + 仅发送人和moderators能看到 + No comment provided by engineer. + + + Only you and moderators see it + 只有你和moderators能看到 + No comment provided by engineer. + Only you can add message reactions. 只有您可以添加消息回应。 @@ -4462,7 +5493,7 @@ Requires compatible VPN. Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) - 只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除)。 + 只有您可以不可撤回地删除消息(您的联系人可以将它们标记为删除) No comment provided by engineer. @@ -4487,7 +5518,7 @@ Requires compatible VPN. Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) - 只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除)。 + 只有您的联系人才能不可撤回地删除消息(您可以将它们标记为删除) No comment provided by engineer. @@ -4508,13 +5539,18 @@ Requires compatible VPN. Open 打开 - No comment provided by engineer. + alert action Open Settings 打开设置 No comment provided by engineer. + + Open changes + 打开更改 + No comment provided by engineer. + Open chat 打开聊天 @@ -4525,31 +5561,43 @@ Requires compatible VPN. 打开聊天控制台 authentication reason + + Open conditions + 打开条款 + No comment provided by engineer. + Open group 打开群 No comment provided by engineer. + + Open link? + alert title + Open migration to another device + 打开迁移到另一台设备 authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - 打开用户个人资料 - authentication reason - - - Open-source protocol and code – anybody can run the servers. - 开源协议和代码——任何人都可以运行服务器。 - No comment provided by engineer. - Opening app… + 正在打开应用程序… + No comment provided by engineer. + + + Operator + 运营方 + No comment provided by engineer. + + + Operator server + 运营方服务器 + alert title + + + Or import archive file + 或者导入或者导入压缩文件 No comment provided by engineer. @@ -4572,14 +5620,25 @@ Requires compatible VPN. 或者显示此码 No comment provided by engineer. + + Or to share privately + 或者私下分享 + No comment provided by engineer. + + + Organize chats into lists + 将聊天组织到列表 + No comment provided by engineer. + Other 其他 No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. + + Other file errors: +%@ + alert message PING count @@ -4616,6 +5675,11 @@ Requires compatible VPN. 密码已设置! No comment provided by engineer. + + Password + 密码 + No comment provided by engineer. + Password to show 显示密码 @@ -4623,6 +5687,7 @@ Requires compatible VPN. Past member %@ + 前任成员 %@ past/unknown group member @@ -4647,15 +5712,11 @@ Requires compatible VPN. Pending + 待定 No comment provided by engineer. - - People can connect to you only via the links you share. - 人们只能通过您共享的链接与您建立联系。 - No comment provided by engineer. - - - Periodically + + Periodic 定期 No comment provided by engineer. @@ -4671,10 +5732,12 @@ Requires compatible VPN. Play from the chat list. + 从聊天列表播放。 No comment provided by engineer. Please ask your contact to enable calls. + 请要求您的联系人开通通话功能。 No comment provided by engineer. @@ -4685,6 +5748,8 @@ Requires compatible VPN. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + 请检查移动设备和桌面是否连接到同一本地网络,以及桌面防火墙是否允许连接。 +请与开发人员分享任何其他问题。 No comment provided by engineer. @@ -4710,6 +5775,8 @@ Please share any other issues with the developers. Please contact developers. Error: %@ + 请联系开发人员。 +错误:%@ No comment provided by engineer. @@ -4752,11 +5819,27 @@ Error: %@ 请安全地保存密码,如果您丢失了密码,您将无法更改它。 No comment provided by engineer. + + Please try to disable and re-enable notfications. + token info + + + Please wait for token activation to complete. + token info + + + Please wait for token to be registered. + token info + Polish interface 波兰语界面 No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect 服务器地址中的证书指纹可能不正确 @@ -4767,16 +5850,15 @@ Error: %@ 保留最后的消息草稿及其附件。 No comment provided by engineer. - - Preset server - 预设服务器 - No comment provided by engineer. - Preset server address 预设服务器地址 No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview 预览 @@ -4784,6 +5866,7 @@ Error: %@ Previously connected servers + 以前连接的服务器 No comment provided by engineer. @@ -4791,22 +5874,42 @@ Error: %@ 隐私和安全 No comment provided by engineer. + + Privacy for your customers. + 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 私密文件名 No comment provided by engineer. + + Private media file names. + No comment provided by engineer. + Private message routing + 私有消息路由 No comment provided by engineer. Private message routing 🚀 + 私有消息路由 🚀 No comment provided by engineer. @@ -4816,10 +5919,12 @@ Error: %@ Private routing + 专用路由 No comment provided by engineer. Private routing error + 专用路由错误 No comment provided by engineer. @@ -4837,15 +5942,6 @@ Error: %@ 个人资料图 No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - 显示名: - No comment provided by engineer. - Profile password 个人资料密码 @@ -4853,12 +5949,13 @@ Error: %@ Profile theme + 个人资料主题 No comment provided by engineer. Profile update will be sent to your contacts. 个人资料更新将被发送给您的联系人。 - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4880,8 +5977,13 @@ Error: %@ 禁止消息回应。 No comment provided by engineer. + + Prohibit reporting messages to moderators. + No comment provided by engineer. + Prohibit sending SimpleX links. + 禁止发送 SimpleX 链接。 No comment provided by engineer. @@ -4906,6 +6008,7 @@ Error: %@ Protect IP address + 保护 IP 地址 No comment provided by engineer. @@ -4916,6 +6019,8 @@ Error: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. + 保护您的 IP 地址免受联系人选择的消息中继的攻击。 +在*网络和服务器*设置中启用。 No comment provided by engineer. @@ -4935,10 +6040,16 @@ Enable in *Network & servers* settings. Proxied + 代理 No comment provided by engineer. Proxied servers + 代理服务器 + No comment provided by engineer. + + + Proxy requires password No comment provided by engineer. @@ -4948,6 +6059,7 @@ Enable in *Network & servers* settings. Push server + 推送服务器 No comment provided by engineer. @@ -4962,6 +6074,7 @@ Enable in *Network & servers* settings. Reachable chat toolbar + 可访问的聊天工具栏 No comment provided by engineer. @@ -4979,26 +6092,21 @@ Enable in *Network & servers* settings. 阅读更多 No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 在 [用户指南](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) 中阅读更多内容。 - 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)。 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. - 在我们的 GitHub 仓库中阅读更多内容。 - 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) 中阅读更多信息。 @@ -5011,6 +6119,7 @@ Enable in *Network & servers* settings. Receive errors + 接收错误 No comment provided by engineer. @@ -5035,14 +6144,17 @@ Enable in *Network & servers* settings. Received messages + 收到的消息 No comment provided by engineer. Received reply + 已收到回复 No comment provided by engineer. Received total + 接收总数 No comment provided by engineer. @@ -5062,6 +6174,7 @@ Enable in *Network & servers* settings. Recent history and improved [directory bot](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). + 最近的历史记录和改进的 [目录机器人](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). No comment provided by engineer. @@ -5076,6 +6189,7 @@ Enable in *Network & servers* settings. Reconnect + 重新连接 No comment provided by engineer. @@ -5085,18 +6199,22 @@ Enable in *Network & servers* settings. Reconnect all servers + 重新连接所有服务器 No comment provided by engineer. Reconnect all servers? + 重新连接所有服务器? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + 重新连接服务器以强制发送信息。它使用额外的流量。 No comment provided by engineer. Reconnect server? + 重新连接服务器? No comment provided by engineer. @@ -5119,11 +6237,23 @@ Enable in *Network & servers* settings. 减少电池使用量 No comment provided by engineer. + + Register + No comment provided by engineer. + + + Register notification token? + token info + + + Registered + token status text + Reject 拒绝 reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5150,8 +6280,13 @@ Enable in *Network & servers* settings. 移除 No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image + 移除图片 No comment provided by engineer. @@ -5214,6 +6349,46 @@ Enable in *Network & servers* settings. 回复 chat item action + + Report + chat item action + + + Report content: only group moderators will see it. + report reason + + + Report member profile: only group moderators will see it. + report reason + + + Report other: only group moderators will see it. + report reason + + + Report reason? + No comment provided by engineer. + + + Report spam: only group moderators will see it. + report reason + + + Report violation: only group moderators will see it. + report reason + + + Report: %@ + report in notification + + + Reporting messages to moderators is prohibited. + No comment provided by engineer. + + + Reports + No comment provided by engineer. + Required 必须 @@ -5226,14 +6401,17 @@ Enable in *Network & servers* settings. Reset all hints + 重置所有提示 No comment provided by engineer. Reset all statistics + 重置所有统计信息 No comment provided by engineer. Reset all statistics? + 重置所有统计信息? No comment provided by engineer. @@ -5243,6 +6421,7 @@ Enable in *Network & servers* settings. Reset to app theme + 重置为应用程序主题 No comment provided by engineer. @@ -5252,6 +6431,7 @@ Enable in *Network & servers* settings. Reset to user theme + 重置为用户主题 No comment provided by engineer. @@ -5294,19 +6474,24 @@ Enable in *Network & servers* settings. 揭示 chat item action + + Review conditions + 审阅条款 + No comment provided by engineer. + Revoke - 撤销 + 吊销 No comment provided by engineer. Revoke file - 撤销文件 + 吊销文件 cancel file action Revoke file? - 撤销文件? + 吊销文件? No comment provided by engineer. @@ -5316,15 +6501,21 @@ Enable in *Network & servers* settings. Run chat - 运行聊天程序 + 运行聊天 No comment provided by engineer. SMP server + SMP 服务器 + No comment provided by engineer. + + + SOCKS proxy No comment provided by engineer. Safely receive files + 安全接收文件 No comment provided by engineer. @@ -5335,17 +6526,18 @@ Enable in *Network & servers* settings. Save 保存 - chat item action + alert button +chat item action Save (and notify contacts) 保存(并通知联系人) - No comment provided by engineer. + alert button Save and notify contact 保存并通知联系人 - No comment provided by engineer. + alert button Save and notify group members @@ -5354,6 +6546,7 @@ Enable in *Network & servers* settings. Save and reconnect + 保存并重新连接 No comment provided by engineer. @@ -5361,21 +6554,16 @@ Enable in *Network & servers* settings. 保存和更新组配置文件 No comment provided by engineer. - - Save archive - 保存存档 - No comment provided by engineer. - - - Save auto-accept settings - 保存自动接受设置 - No comment provided by engineer. - Save group profile 保存群组资料 No comment provided by engineer. + + Save list + 保存列表 + No comment provided by engineer. + Save passphrase and open chat 保存密码并打开聊天 @@ -5389,7 +6577,7 @@ Enable in *Network & servers* settings. Save preferences? 保存偏好设置? - No comment provided by engineer. + alert title Save profile password @@ -5404,18 +6592,18 @@ Enable in *Network & servers* settings. Save servers? 保存服务器? - No comment provided by engineer. - - - Save settings? - 保存设置? - No comment provided by engineer. + alert title Save welcome message? 保存欢迎信息? No comment provided by engineer. + + Save your profile? + 保存您的个人资料? + alert title + Saved 已保存 @@ -5436,12 +6624,19 @@ Enable in *Network & servers* settings. 已保存的消息 message info title + + Saving %lld messages + 正在保存 %lld 条消息 + No comment provided by engineer. + Scale + 规模 No comment provided by engineer. Scan / Paste link + 扫描 / 粘贴链接 No comment provided by engineer. @@ -5486,6 +6681,7 @@ Enable in *Network & servers* settings. Secondary + 二级 No comment provided by engineer. @@ -5495,6 +6691,7 @@ Enable in *Network & servers* settings. Secured + 担保 No comment provided by engineer. @@ -5512,12 +6709,18 @@ Enable in *Network & servers* settings. 选择 chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld + 选定的 %lld No comment provided by engineer. Selected chat preferences prohibit this message. + 选定的聊天首选项禁止此消息。 No comment provided by engineer. @@ -5567,6 +6770,7 @@ Enable in *Network & servers* settings. Send errors + 发送错误 No comment provided by engineer. @@ -5581,14 +6785,17 @@ Enable in *Network & servers* settings. 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 地址受到保护并且您或目标服务器不支持私有路由时,直接发送消息。 No comment provided by engineer. Send messages directly when your or destination server does not support private routing. + 当您或目标服务器不支持私有路由时,直接发送消息。 No comment provided by engineer. @@ -5596,9 +6803,8 @@ Enable in *Network & servers* settings. 发送通知 No comment provided by engineer. - - Send notifications: - 发送通知: + + Send private reports No comment provided by engineer. @@ -5624,7 +6830,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. 发送人已取消文件传输。 - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -5683,6 +6889,7 @@ Enable in *Network & servers* settings. Sent directly + 直接发送 No comment provided by engineer. @@ -5697,6 +6904,7 @@ Enable in *Network & servers* settings. Sent messages + 已发送的消息 No comment provided by engineer. @@ -5706,28 +6914,54 @@ Enable in *Network & servers* settings. Sent reply + 已发送回复 No comment provided by engineer. Sent total + 发送总数 No comment provided by engineer. Sent via proxy + 通过代理发送 No comment provided by engineer. + + Server + No comment provided by engineer. + + + Server added to operator %@. + alert message + Server address + 服务器地址 No comment provided by engineer. Server address is incompatible with network settings. + 服务器地址与网络设置不兼容。 srv error text. Server address is incompatible with network settings: %@. + 服务器地址与网络设置不兼容:%@。 No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password 服务器需要授权才能创建队列,检查密码 @@ -5745,14 +6979,17 @@ Enable in *Network & servers* settings. Server type + 服务器类型 No comment provided by engineer. Server version is incompatible with network settings. + 服务器版本与网络设置不兼容。 srv error text Server version is incompatible with your app: %@. + 服务器版本与你的应用程序不兼容:%@。 No comment provided by engineer. @@ -5762,10 +6999,12 @@ Enable in *Network & servers* settings. Servers info + 服务器信息 No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + 服务器统计信息将被重置 - 此操作无法撤消! No comment provided by engineer. @@ -5778,6 +7017,10 @@ Enable in *Network & servers* settings. 设定1天 No comment provided by engineer. + + Set chat name… + No comment provided by engineer. + Set contact name… 设置联系人姓名…… @@ -5785,6 +7028,7 @@ Enable in *Network & servers* settings. Set default theme + 设置默认主题 No comment provided by engineer. @@ -5797,6 +7041,10 @@ Enable in *Network & servers* settings. 设置它以代替系统身份验证。 No comment provided by engineer. + + Set message expiration in chats. + No comment provided by engineer. + Set passcode 设置密码 @@ -5827,6 +7075,10 @@ Enable in *Network & servers* settings. 设置 No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images 改变个人资料图形状 @@ -5835,25 +7087,39 @@ Enable in *Network & servers* settings. Share 分享 - chat item action + alert action +chat item action Share 1-time link 分享一次性链接 No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address 分享地址 No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? 与联系人分享地址? - No comment provided by engineer. + alert title Share from other apps. + 从其他应用程序共享。 No comment provided by engineer. @@ -5861,6 +7127,10 @@ Enable in *Network & servers* settings. 分享链接 No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link 分享此一次性邀请链接 @@ -5868,6 +7138,7 @@ Enable in *Network & servers* settings. Share to SimpleX + 分享到 SimpleX No comment provided by engineer. @@ -5875,6 +7146,10 @@ Enable in *Network & servers* settings. 与联系人分享 No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code 显示二维码 @@ -5897,10 +7172,12 @@ Enable in *Network & servers* settings. Show message status + 显示消息状态 No comment provided by engineer. Show percentage + 显示百分比 No comment provided by engineer. @@ -5910,6 +7187,7 @@ Enable in *Network & servers* settings. Show → on messages sent via private routing. + 显示 → 通过专用路由发送的信息. No comment provided by engineer. @@ -5919,6 +7197,7 @@ Enable in *Network & servers* settings. SimpleX + SimpleX No comment provided by engineer. @@ -5926,6 +7205,11 @@ Enable in *Network & servers* settings. SimpleX 地址 No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat 与 Flux 达成了协议,将由 Flux 控制的服务器纳入 SimpleX 应用。 + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat 的安全性 由 Trail of Bits 审核。 @@ -5956,6 +7240,21 @@ Enable in *Network & servers* settings. SimpleX 地址 No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + 可以通过任何消息应用安全分享 SimpleX 地址和一次性链接。 + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX 地址或一次性链接? + No comment provided by engineer. + + + SimpleX channel link + SimpleX 频道链接 + simplex link type + SimpleX contact address SimpleX 联系地址 @@ -5963,12 +7262,12 @@ Enable in *Network & servers* settings. SimpleX encrypted message or connection event - SimpleX 加密消息或连接项目 + SimpleX 加密的消息或连接事件 notification SimpleX group link - SimpleX 群组链接 + SimpleX 群链接 simplex link type @@ -5976,8 +7275,8 @@ Enable in *Network & servers* settings. SimpleX 链接 chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. 此群禁止 SimpleX 链接。 No comment provided by engineer. @@ -5991,6 +7290,11 @@ Enable in *Network & servers* settings. SimpleX 一次性邀请 simplex link type + + SimpleX protocols reviewed by Trail of Bits. + SimpleX 协议由 Trail of Bits 审阅。 + No comment provided by engineer. + Simplified incognito mode 简化的隐身模式 @@ -5998,6 +7302,7 @@ Enable in *Network & servers* settings. Size + 大小 No comment provided by engineer. @@ -6017,10 +7322,17 @@ Enable in *Network & servers* settings. Soft + blur media + + Some app settings were not migrated. + 部分应用设置未被迁移。 + No comment provided by engineer. + Some file(s) were not exported: + 某些文件未导出: No comment provided by engineer. @@ -6030,16 +7342,27 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: + 导入过程中出现一些非致命错误: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody 某人 notification title + + Spam + blocking reason +report reason + Square, circle, or anything in between. - 方形、圆形、或两者之间的任意形状 + 方形、圆形、或两者之间的任意形状. No comment provided by engineer. @@ -6059,10 +7382,12 @@ Enable in *Network & servers* settings. Starting from %@. + 从 %@ 开始。 No comment provided by engineer. Statistics + 统计 No comment provided by engineer. @@ -6080,11 +7405,6 @@ Enable in *Network & servers* settings. 停止聊天程序 No comment provided by engineer. - - Stop chat to enable database actions - 停止聊天以启用数据库操作 - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. 停止聊天以便导出、导入或删除聊天数据库。在聊天停止期间,您将无法收发消息。 @@ -6113,20 +7433,25 @@ Enable in *Network & servers* settings. Stop sharing 停止分享 - No comment provided by engineer. + alert action Stop sharing address? 停止分享地址? - No comment provided by engineer. + alert title Stopping chat 正在停止聊天 No comment provided by engineer. + + Storage + No comment provided by engineer. + Strong + 加粗 blur media @@ -6136,14 +7461,17 @@ Enable in *Network & servers* settings. Subscribed + 已订阅 No comment provided by engineer. Subscription errors + 订阅错误 No comment provided by engineer. Subscriptions ignored + 忽略订阅 No comment provided by engineer. @@ -6151,6 +7479,14 @@ Enable in *Network & servers* settings. 支持 SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System 系统 @@ -6163,6 +7499,7 @@ Enable in *Network & servers* settings. TCP connection + TCP 连接 No comment provided by engineer. @@ -6170,6 +7507,10 @@ Enable in *Network & servers* settings. TCP 连接超时 No comment provided by engineer. + + TCP port for messaging + No comment provided by engineer. + TCP_KEEPCNT TCP_KEEPCNT @@ -6185,11 +7526,19 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture 拍照 No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button 点击按钮 @@ -6227,13 +7576,18 @@ Enable in *Network & servers* settings. Temporary file error - No comment provided by engineer. + 临时文件错误 + file error alert title Test failed at step %@. 在步骤 %@ 上测试失败。 server test failure + + Test notifications + No comment provided by engineer. + Test server 测试服务器 @@ -6247,7 +7601,7 @@ Enable in *Network & servers* settings. Tests failed! 测试失败! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6264,11 +7618,6 @@ Enable in *Network & servers* settings. 感谢用户——通过 Weblate 做出贡献! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - 第一个没有任何用户标识符的平台 - 隐私设计. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6281,8 +7630,13 @@ It can happen because of some bug or when the connection is compromised.该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。 No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). + 该应用程序将要求确认从未知文件服务器(.onion 除外)下载。 No comment provided by engineer. @@ -6295,6 +7649,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, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! 您接受的连接将被取消! @@ -6315,6 +7673,11 @@ It can happen because of some bug or when the connection is compromised.加密正在运行,不需要新的加密协议。这可能会导致连接错误! No comment provided by engineer. + + The future of messaging + 下一代私密通讯软件 + No comment provided by engineer. + The hash of the previous message is different. 上一条消息的散列不同。 @@ -6332,15 +7695,12 @@ It can happen because of some bug or when the connection is compromised. The messages will be deleted for all members. + 将删除所有成员的消息。 No comment provided by engineer. The messages will be marked as moderated for all members. - No comment provided by engineer. - - - The next generation of private messaging - 下一代私密通讯软件 + 对于所有成员,这些消息将被标记为已审核。 No comment provided by engineer. @@ -6348,9 +7708,12 @@ It can happen because of some bug or when the connection is compromised.旧数据库在迁移过程中没有被移除,可以删除。 No comment provided by engineer. - - The profile is only shared with your contacts. - 该资料仅与您的联系人共享。 + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The second preset operator in the app! No comment provided by engineer. @@ -6368,13 +7731,26 @@ It can happen because of some bug or when the connection is compromised.您当前聊天资料 **%@** 的新连接服务器。 No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + 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. + No comment provided by engineer. + Themes + 主题 + No comment provided by engineer. + + + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -6397,6 +7773,10 @@ It can happen because of some bug or when the connection is compromised.此操作无法撤消——早于所选的发送和接收的消息将被删除。 这可能需要几分钟时间。 No comment provided by engineer. + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + alert message + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. 此操作无法撤消——您的个人资料、联系人、消息和文件将不可撤回地丢失。 @@ -6442,8 +7822,17 @@ It can happen because of some bug or when the connection is compromised.这是你自己的一次性链接! 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. + No comment provided by engineer. + 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. No comment provided by engineer. @@ -6453,6 +7842,7 @@ It can happen because of some bug or when the connection is compromised. Title + 标题 No comment provided by engineer. @@ -6475,9 +7865,8 @@ It can happen because of some bug or when the connection is compromised.建立新连接 No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - 为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。 + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6487,6 +7876,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 服务器来传递邮件。 No comment provided by engineer. @@ -6496,6 +7886,23 @@ You will be prompted to complete authentication before this feature is enabled.< 在启用此功能之前,系统将提示您完成身份验证。 No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + 为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。 + No comment provided by engineer. + + + To receive + No comment provided by engineer. + + + To record speech please grant permission to use Microphone. + No comment provided by engineer. + + + To record video please grant permission to use Camera. + No comment provided by engineer. + To record voice message please grant permission to use Microphone. 请授权使用麦克风以录制语音消息。 @@ -6506,11 +7913,19 @@ You will be prompted to complete authentication before this feature is enabled.< 要显示您的隐藏的个人资料,请在**您的聊天个人资料**页面的搜索字段中输入完整密码。 No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. 为了支持即时推送通知,聊天数据库必须被迁移。 No comment provided by engineer. + + 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. 要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。 @@ -6518,6 +7933,7 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle chat list: + 切换聊天列表: No comment provided by engineer. @@ -6525,12 +7941,18 @@ You will be prompted to complete authentication before this feature is enabled.< 在连接时切换隐身模式。 No comment provided by engineer. + + Token status: %@. + token status + Toolbar opacity + 工具栏不透明度 No comment provided by engineer. Total + 共计 No comment provided by engineer. @@ -6540,6 +7962,7 @@ You will be prompted to complete authentication before this feature is enabled.< Transport sessions + 传输会话 No comment provided by engineer. @@ -6554,6 +7977,7 @@ You will be prompted to complete authentication before this feature is enabled.< Turkish interface + 土耳其语界面 No comment provided by engineer. @@ -6596,6 +8020,10 @@ You will be prompted to complete authentication before this feature is enabled.< 解封成员吗? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state 未预料的迁移状态 @@ -6643,7 +8071,8 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + 未知服务器! + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6680,13 +8109,17 @@ To connect, please ask your contact to create another connection link and check Unmute 取消静音 - swipe action + notification label action Unread 未读 swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. 给新成员发送了最多 100 条历史消息。 @@ -6709,6 +8142,11 @@ To connect, please ask your contact to create another connection link and check Update settings? + 更新设置? + No comment provided by engineer. + + + Updated conditions No comment provided by engineer. @@ -6723,6 +8161,7 @@ To connect, please ask your contact to create another connection link and check Upload errors + 上传错误 No comment provided by engineer. @@ -6737,10 +8176,12 @@ To connect, please ask your contact to create another connection link and check Uploaded + 已上传 No comment provided by engineer. Uploaded files + 已上传的文件 No comment provided by engineer. @@ -6748,16 +8189,32 @@ To connect, please ask your contact to create another connection link and check 正在上传存档 No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts 使用 .onion 主机 No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? 使用 SimpleX Chat 服务器? No comment provided by engineer. + + Use TCP port %@ when no port is specified. + No comment provided by engineer. + + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat 使用聊天 @@ -6768,6 +8225,14 @@ To connect, please ask your contact to create another connection link and check 使用当前配置文件 No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections 用于新连接 @@ -6790,14 +8255,17 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? + 仅使用本地通知? No comment provided by engineer. 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. @@ -6805,22 +8273,35 @@ To connect, please ask your contact to create another connection link and check 使用服务器 No comment provided by engineer. + + Use servers + No comment provided by engineer. + + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. - 通话时使用本应用 + 通话时使用本应用. No comment provided by engineer. Use the app with one hand. + 用一只手使用应用程序。 No comment provided by engineer. - - User profile - 用户资料 + + Use web port No comment provided by engineer. User selection + 用户选择 + No comment provided by engineer. + + + Username No comment provided by engineer. @@ -6893,11 +8374,19 @@ To connect, please ask your contact to create another connection link and check 最大 1gb 的视频和文件 No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code 查看安全码 No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history 可见的历史 @@ -6913,8 +8402,8 @@ To connect, please ask your contact to create another connection link and check 语音信息在此聊天中被禁止。 No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. 语音信息在该群组中被禁用。 No comment provided by engineer. @@ -6935,6 +8424,7 @@ To connect, please ask your contact to create another connection link and check Waiting for desktop... + 正在等待桌面... No comment provided by engineer. @@ -6954,15 +8444,17 @@ To connect, please ask your contact to create another connection link and check Wallpaper accent + 壁纸装饰 No comment provided by engineer. Wallpaper background + 壁纸背景 No comment provided by engineer. Warning: starting chat on multiple devices is not supported and will cause message delivery failures - 警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败。 + 警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败 No comment provided by engineer. @@ -7005,9 +8497,8 @@ To connect, please ask your contact to create another connection link and check 连接音频和视频通话时。 No comment provided by engineer. - - When people request to connect, you can accept or reject it. - 当人们请求连接时,您可以接受或拒绝它。 + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7047,11 +8538,13 @@ 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. + 如果没有 Tor 或 VPN,您的 IP 地址将对文件服务器可见。 No comment provided by engineer. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + 如果没有 Tor 或 VPN,您的 IP 地址将对以下 XFTP 中继可见:%@。 + alert message Wrong database passphrase @@ -7060,10 +8553,12 @@ To connect, please ask your contact to create another connection link and check Wrong key or unknown connection - most likely this connection is deleted. + 密钥错误或连接未知 - 很可能此连接已被删除。 snd error text Wrong key or unknown file chunk address - most likely file is deleted. + 密钥错误或文件块地址未知 - 很可能文件已删除。 file error text @@ -7073,15 +8568,12 @@ To connect, please ask your contact to create another connection link and check XFTP server - No comment provided by engineer. - - - You - + XFTP 服务器 No comment provided by engineer. You **must not** use the same database on two devices. + 您 **不得** 在两台设备上使用相同的数据库。 No comment provided by engineer. @@ -7104,8 +8596,13 @@ To connect, please ask your contact to create another connection link and check 您已经连接到 %@。 No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. + 您已连接到 %@。 No comment provided by engineer. @@ -7115,14 +8612,17 @@ To connect, please ask your contact to create another connection link and check You are already in group %@. + 您已在组 %@ 中。 No comment provided by engineer. You are already joining the group %@. + 您已加入组 %@。 No comment provided by engineer. You are already joining the group via this link! + 您已经通过此链接加入群组! No comment provided by engineer. @@ -7133,6 +8633,8 @@ To connect, please ask your contact to create another connection link and check You are already joining the group! Repeat join request? + 您已经加入了这个群组! +重复加入请求? No comment provided by engineer. @@ -7147,6 +8649,7 @@ Repeat join request? You are not connected to these servers. Private routing is used to deliver messages to them. + 您未连接到这些服务器。私有路由用于向他们发送消息。 No comment provided by engineer. @@ -7156,6 +8659,11 @@ Repeat join request? You can change it in Appearance settings. + 您可以在外观设置中更改它。 + No comment provided by engineer. + + + You can configure servers via settings. No comment provided by engineer. @@ -7195,6 +8703,11 @@ Repeat join request? You can send messages to %@ from Archived contacts. + 您可以从存档的联系人向%@发送消息。 + No comment provided by engineer. + + + You can set connection name, to remember who the link was shared with. No comment provided by engineer. @@ -7212,11 +8725,6 @@ Repeat join request? 您可以与您的联系人分享该地址,让他们与 **%@** 联系。 No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app 您可以通过应用程序设置/数据库或重新启动应用程序开始聊天 @@ -7224,6 +8732,7 @@ Repeat join request? You can still view conversation with %@ in the list of chats. + 您仍然可以在聊天列表中查看与 %@的对话。 No comment provided by engineer. @@ -7239,23 +8748,23 @@ Repeat join request? You can view invitation link again in connection details. 您可以在连接详情中再次查看邀请链接。 - No comment provided by engineer. + alert message You can't send messages! 您无法发送消息! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - 您可以控制接收信息使用的服务器,您的联系人则使用您发送信息时所使用的服务器。 - 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 already requested connection via this address! 你已经请求通过此地址进行连接! @@ -7264,6 +8773,8 @@ Repeat join request? You have already requested connection! Repeat connection request? + 您已经请求连接了! +重复连接请求? No comment provided by engineer. @@ -7288,10 +8799,12 @@ Repeat connection request? You may migrate the exported database. + 您可以迁移导出的数据库。 No comment provided by engineer. You may save the exported archive. + 您可以保存导出的档案。 No comment provided by engineer. @@ -7301,6 +8814,7 @@ Repeat connection request? You need to allow your contact to call to be able to call them. + 您需要允许您的联系人呼叫才能呼叫他们。 No comment provided by engineer. @@ -7318,6 +8832,10 @@ Repeat connection request? 您发送了群组邀请 No comment provided by engineer. + + You should receive notifications. + token info + You will be connected to group when the group host's device is online, please wait or check later! 您将在组主设备上线时连接到该群组,请稍等或稍后再检查! @@ -7325,6 +8843,7 @@ Repeat connection request? You will be connected when group link host's device is online, please wait or check later! + 当 Group Link Host 的设备在线时,您将被连接,请稍候或稍后检查! No comment provided by engineer. @@ -7352,6 +8871,10 @@ Repeat connection request? 当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。 No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. 您将停止接收来自该群组的消息。聊天记录将被保留。 @@ -7372,31 +8895,16 @@ Repeat connection request? 您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人 No comment provided by engineer. - - Your %@ servers - 您的 %@ 服务器 - No comment provided by engineer. - Your ICE servers 您的 ICE 服务器 No comment provided by engineer. - - Your SMP servers - 您的 SMP 服务器 - No comment provided by engineer. - Your SimpleX address 您的 SimpleX 地址 No comment provided by engineer. - - Your XFTP servers - 您的 XFTP 服务器 - No comment provided by engineer. - Your calls 您的通话 @@ -7412,11 +8920,19 @@ Repeat connection request? 您的聊天数据库未加密——设置密码来加密。 No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles 您的聊天资料 No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). 您的联系人发送的文件大于当前支持的最大大小 (%@)。 @@ -7432,6 +8948,10 @@ Repeat connection request? 与您的联系人保持连接。 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. 您当前的聊天数据库将被删除并替换为导入的数据库。 @@ -7454,6 +8974,7 @@ Repeat connection request? Your profile + 您的个人资料 No comment provided by engineer. @@ -7461,33 +8982,34 @@ Repeat connection request? 您的个人资料 **%@** 将被共享。 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 only shared with your contacts. + 该资料仅与您的联系人共享。 No comment provided by engineer. - - Your profile, contacts and delivered messages are stored on your device. - 您的资料、联系人和发送的消息存储在您的设备上。 + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + 您的资料存储在您的设备上并仅与您的联系人共享。 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 random profile 您的随机资料 No comment provided by engineer. - - Your server - 您的服务器 - No comment provided by engineer. - Your server address 您的服务器地址 No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings 您的设置 @@ -7528,6 +9050,10 @@ SimpleX 服务器无法看到您的资料。 已接受通话 call status + + accepted invitation + chat list item title + admin 管理员 @@ -7560,10 +9086,16 @@ SimpleX 服务器无法看到您的资料。 and %lld other events + 和 %lld 其他事件 + No comment provided by engineer. + + + archived report No comment provided by engineer. attempts + 尝试 No comment provided by engineer. @@ -7599,7 +9131,8 @@ SimpleX 服务器无法看到您的资料。 blocked by admin 由管理员封禁 - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -7608,6 +9141,7 @@ SimpleX 服务器无法看到您的资料。 call + 呼叫 No comment provided by engineer. @@ -7713,7 +9247,7 @@ SimpleX 服务器无法看到您的资料。 connecting… 连接中…… - chat list item title + No comment provided by engineer. connection established @@ -7727,6 +9261,7 @@ SimpleX 服务器无法看到您的资料。 contact %1$@ changed to %2$@ + 联系人 %1$@ 已更改为 %2$@ profile update event chat item @@ -7761,12 +9296,14 @@ SimpleX 服务器无法看到您的资料。 decryption errors + 解密错误 No comment provided by engineer. default (%@) 默认 (%@) - pref value + delete after time +pref value default (no) @@ -7815,6 +9352,7 @@ SimpleX 服务器无法看到您的资料。 duplicates + 副本 No comment provided by engineer. @@ -7892,13 +9430,9 @@ SimpleX 服务器无法看到您的资料。 错误 No comment provided by engineer. - - event happened - 发生的事 - No comment provided by engineer. - expired + 过期 No comment provided by engineer. @@ -7933,6 +9467,7 @@ SimpleX 服务器无法看到您的资料。 inactive + 无效 No comment provided by engineer. @@ -7977,6 +9512,7 @@ SimpleX 服务器无法看到您的资料。 invite + 邀请 No comment provided by engineer. @@ -8026,6 +9562,7 @@ SimpleX 服务器无法看到您的资料。 member %1$@ changed to %2$@ + 成员 %1$@ 已更改为 %2$@ profile update event chat item @@ -8035,6 +9572,7 @@ SimpleX 服务器无法看到您的资料。 message + 消息 No comment provided by engineer. @@ -8062,19 +9600,19 @@ SimpleX 服务器无法看到您的资料。 由 %@ 审核 marked deleted chat item preview text + + moderator + member role + months time unit - - mute - No comment provided by engineer. - never 从不 - No comment provided by engineer. + delete after time new message @@ -8105,8 +9643,8 @@ SimpleX 服务器无法看到您的资料。 off 关闭 enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -8125,10 +9663,12 @@ SimpleX 服务器无法看到您的资料。 other + 其他 No comment provided by engineer. other errors + 其他错误 No comment provided by engineer. @@ -8146,6 +9686,14 @@ SimpleX 服务器无法看到您的资料。 点对点 No comment provided by engineer. + + pending + No comment provided by engineer. + + + pending approval + No comment provided by engineer. + quantum resistant e2e encryption 抗量子端到端加密 @@ -8161,6 +9709,10 @@ SimpleX 服务器无法看到您的资料。 已受到确认…… No comment provided by engineer. + + rejected + No comment provided by engineer. + rejected call 拒接来电 @@ -8191,6 +9743,10 @@ SimpleX 服务器无法看到您的资料。 已将您移除 rcv group event chat item + + requested to connect + chat list item title + saved 已保存 @@ -8198,10 +9754,12 @@ SimpleX 服务器无法看到您的资料。 saved from %@ + 保存自 %@ No comment provided by engineer. search + 搜索 No comment provided by engineer. @@ -8233,6 +9791,9 @@ SimpleX 服务器无法看到您的资料。 server queue info: %1$@ last received msg: %2$@ + 服务器队列信息: %1$@ + +上次收到的消息: %2$@ queue info @@ -8267,6 +9828,7 @@ last received msg: %2$@ unblocked %@ + 未阻止 %@ rcv group event chat item @@ -8276,6 +9838,7 @@ last received msg: %2$@ unknown servers + 未知服务器 No comment provided by engineer. @@ -8283,12 +9846,9 @@ last received msg: %2$@ 未知状态 No comment provided by engineer. - - unmute - No comment provided by engineer. - unprotected + 未受保护 No comment provided by engineer. @@ -8303,6 +9863,7 @@ last received msg: %2$@ v%@ + v%@ No comment provided by engineer. @@ -8332,6 +9893,7 @@ last received msg: %2$@ video + 视频 No comment provided by engineer. @@ -8361,6 +9923,7 @@ last received msg: %2$@ when IP hidden + 当 IP 隐藏时 No comment provided by engineer. @@ -8385,6 +9948,7 @@ last received msg: %2$@ you blocked %@ + 你阻止了%@ snd group event chat item @@ -8429,6 +9993,7 @@ last received msg: %2$@ you unblocked %@ + 您解封了 %@ snd group event chat item @@ -8445,7 +10010,7 @@ last received msg: %2$@
- +
@@ -8465,6 +10030,7 @@ last received msg: %2$@ SimpleX uses local network access to allow using user chat profile via desktop app on the same network. + SimpleX 使用本地网络访问,允许通过同一网络上的桌面应用程序使用用户聊天配置文件。 Privacy - Local Network Usage Description @@ -8481,7 +10047,7 @@ last received msg: %2$@
- +
@@ -8501,176 +10067,243 @@ last received msg: %2$@
+ +
+ +
+ + + %d new events + notification body + + + From %d chat(s) + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + +
- +
SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright © 2024 SimpleX Chat. All rights reserved. + 版权所有 © 2024 SimpleX Chat。保留所有权利。 Copyright (human-readable)
- +
%@ + %@ No comment provided by engineer. App is locked! + 应用程序已锁定! No comment provided by engineer. Cancel + 取消 No comment provided by engineer. Cannot access keychain to save database password + 无法访问钥匙串以保存数据库密码 No comment provided by engineer. Cannot forward message + 无法转发消息 No comment provided by engineer. Comment + 评论 No comment provided by engineer. Currently maximum supported file size is %@. + 当前支持的最大文件大小为 %@。 No comment provided by engineer. Database downgrade required + 需要数据库降级 No comment provided by engineer. Database encrypted! + 数据库已加密! No comment provided by engineer. Database error + 数据库错误 No comment provided by engineer. Database passphrase is different from saved in the keychain. + 数据库密码与保存在钥匙串中的密码不同。 No comment provided by engineer. Database passphrase is required to open chat. + 需要数据库密码才能打开聊天。 No comment provided by engineer. Database upgrade required + 需要升级数据库 No comment provided by engineer. Error preparing file + 准备文件时出错 No comment provided by engineer. Error preparing message + 准备消息时出错 No comment provided by engineer. Error: %@ + 错误:%@ No comment provided by engineer. File error + 文件错误 No comment provided by engineer. Incompatible database version + 不兼容的数据库版本 No comment provided by engineer. Invalid migration confirmation + 无效的迁移确认 No comment provided by engineer. Keychain error + 钥匙串错误 No comment provided by engineer. Large file! + 大文件! No comment provided by engineer. No active profile + 无活动配置文件 No comment provided by engineer. Ok + 好的 No comment provided by engineer. Open the app to downgrade the database. + 打开应用程序以降级数据库。 No comment provided by engineer. Open the app to upgrade the database. + 打开应用程序以升级数据库。 No comment provided by engineer. Passphrase + 密码 No comment provided by engineer. Please create a profile in the SimpleX app + 请在 SimpleX 应用程序中创建配置文件 No comment provided by engineer. Selected chat preferences prohibit this message. + 选定的聊天首选项禁止此消息。 No comment provided by engineer. Sending a message takes longer than expected. + 发送消息所需的时间比预期的要长。 No comment provided by engineer. Sending message… + 正在发送消息… No comment provided by engineer. Share + 共享 No comment provided by engineer. Slow network? + 网络速度慢? No comment provided by engineer. Unknown database error: %@ + 未知数据库错误: %@ No comment provided by engineer. Unsupported format + 不支持的格式 No comment provided by engineer. Wait + 等待 No comment provided by engineer. Wrong database passphrase + 数据库密码错误 No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + 您可以在 "隐私与安全"/"SimpleX Lock "设置中允许共享。 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json index 6416a2d8fa..91977b0744 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "zh-Hans", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 2b8649935c..3ea46ee364 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -109,7 +109,7 @@
%d skipped message(s) - %d錯過了訊息 + 錯過的 %d 則訊息 integrity error chat item @@ -124,17 +124,17 @@ %lld contact(s) selected - %lld 已選擇聯絡人(s) + 已選擇 %lld 個聯絡人 No comment provided by engineer. %lld file(s) with total size of %@ - %lld 檔案(s) 的總共大小為%@ + %lld 個檔案,總共大小 %@ No comment provided by engineer. %lld members - %lld 成員 + %lld 個成員 No comment provided by engineer. @@ -187,23 +187,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **新增新的聯絡人**:建立一次性二維碼或連結連接聯絡人。 - No comment provided by engineer. - **Create link / QR code** for your contact to use. **建立連結 / 二維碼** 讓你的聯絡人使用。 No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **更有私隱**:每20分鐘會檢查一次訊息。裝置權杖與 SimpleX Chat 伺服器分享中,但是不包括你的聯絡人和訊息資料。 No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **最有私隱**:不使用 SimpleX Chat 通知服務器,在後台定期檢查訊息(取決於你使用應用程序的頻率)。 No comment provided by engineer. @@ -217,8 +212,8 @@ **請注意**:如果你忘記了密碼你將不能再次復原或更改密碼。 No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **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. **建議**:裝置權杖和通知都會傳送去 SimpeleX Chat 的通知伺服器,但是不包括訊息內容、大小或傳送者資料。 No comment provided by engineer. @@ -229,7 +224,7 @@ **Warning**: Instant push notifications require passphrase saved in Keychain. - **警告**:即時推送訊息通知需要數據庫的密碼儲存在資料庫中。 + **警告**:即時推送訊息通知需要將數據庫的密碼儲存在資料庫中。 No comment provided by engineer. @@ -1178,8 +1173,8 @@ 私訊 chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. 私訊群組內的成員於這個群組內是禁用的。 No comment provided by engineer. @@ -1198,8 +1193,8 @@ 自動銷毀訊息已被禁止於此聊天室。 No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. 自動銷毀訊息於這個群組內是禁用的。 No comment provided by engineer. @@ -1623,18 +1618,18 @@ 群組內的成員可以不可逆地刪除訊息。 No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. 群組內的成員可以私訊群組內的成員。 No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. 群組內的成員可以傳送自動銷毀的訊息。 No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. 群組內的成員可以傳送語音訊息。 No comment provided by engineer. @@ -1747,8 +1742,8 @@ 下載圖片需要傳送者上線的時候才能下載圖片,請等待對方上線! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam 不受垃圾郵件和濫用行為影響 No comment provided by engineer. @@ -1869,8 +1864,8 @@ 不可逆地刪除訊息於這個聊天室內是禁用的。 No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. 不可逆地刪除訊息於這個群組內是禁用的。 No comment provided by engineer. @@ -2217,8 +2212,8 @@ We will be adding server redundancy to prevent lost messages. Onion 主機不會啟用。 No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. 只有客戶端裝置才會儲存你的個人檔案、聯絡人,群組,所有訊息都會經過**兩層的端對端加密**。 No comment provided by engineer. @@ -2277,8 +2272,8 @@ We will be adding server redundancy to prevent lost messages. 使用終端機開啟對話 authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. 開放源碼協議和程式碼 – 任何人也可以運行伺服器。 No comment provided by engineer. @@ -2317,8 +2312,8 @@ We will be adding server redundancy to prevent lost messages. 將你接收到的連結貼上至下面的框內,以開始你與你的聯絡人對話。 No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. 人們只能在你分享了連結後,才能和你連接。 No comment provided by engineer. @@ -2709,12 +2704,12 @@ We will be adding server redundancy to prevent lost messages. Send link previews - 傳送可以預覽的連結 + 傳送連結預覽 No comment provided by engineer. Send live message - 傳送實況的訊息 + 傳送實時訊息 No comment provided by engineer. @@ -2729,7 +2724,7 @@ We will be adding server redundancy to prevent lost messages. Send questions and ideas - 傳送問題和想法給開發者 + 給開發者提問題和想法 No comment provided by engineer. @@ -2779,7 +2774,7 @@ We will be adding server redundancy to prevent lost messages. Set 1 day - 設定為1天 + 設定為 1 天 No comment provided by engineer. @@ -3010,8 +3005,8 @@ We will be adding server redundancy to prevent lost messages. 感謝你安裝SimpleX Chat! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. 第一個沒有任何用戶識別符的通訊平台 – 以私隱為設計。 No comment provided by engineer. @@ -3027,7 +3022,7 @@ We will be adding server redundancy to prevent lost messages. The connection you accepted will be cancelled! - 你所接受的連接將被取消! + 你接受的連接將被取消! No comment provided by engineer. @@ -3049,8 +3044,8 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging 新一代的私密訊息平台 No comment provided by engineer. @@ -3059,14 +3054,14 @@ We will be adding server redundancy to prevent lost messages. 舊的數據庫在遷移過程中沒有被移除,可以刪除。 No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. 你的個人檔案只會和你的聯絡人分享。 No comment provided by engineer. The sender will NOT be notified - 發送者不會接收到通知 + 發送者不會收到通知 No comment provided by engineer. @@ -3076,12 +3071,12 @@ We will be adding server redundancy to prevent lost messages. 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. @@ -3118,8 +3113,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. 為了保護隱私,而不像是其他平台般需要提取和存儲用戶的 IDs 資料,SimpleX 平台有自家佇列的標識符,這對於你的每個聯絡人也是獨一無二的。 No comment provided by engineer. @@ -3268,7 +3263,7 @@ To connect, please ask your contact to create another connection link and check Use for new connections - 用於新的連接 + 用於新的連線 No comment provided by engineer. @@ -3288,7 +3283,7 @@ To connect, please ask your contact to create another connection link and check Verify connection security - 驗證連接安全性 + 驗證連線安全性 No comment provided by engineer. @@ -3321,8 +3316,8 @@ To connect, please ask your contact to create another connection link and check 語音訊息於這個聊天窒是禁用的。 No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. 語音訊息於這個群組內是禁用的。 No comment provided by engineer. @@ -3455,11 +3450,6 @@ To connect, please ask your contact to create another connection link and check 你可以使用 Markdown 語法以更清楚標明訊息: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - 你可以控制通過哪一個伺服器 **來接收** 你的聯絡人訊息 – 這些伺服器用來接收他們傳送給你的訊息。 - No comment provided by engineer. - You could not be verified; please try again. 你未能通過認證;請再試一次。 @@ -4173,7 +4163,7 @@ SimpleX 伺服器並不會看到你的個人檔案。 via contact address link - 透過聯絡人的邀請連結連接 + 透過聯絡人的邀請連結連線 chat list item description @@ -4183,7 +4173,7 @@ SimpleX 伺服器並不會看到你的個人檔案。 via one-time link - 透過一次性連結連接 + 透過一次性連結連線 chat list item description @@ -4712,7 +4702,7 @@ Available in v5.1 %u messages failed to decrypt. - %u 訊息解密失敗。 + %u 則訊息解密失敗。 No comment provided by engineer. @@ -4791,8 +4781,8 @@ Available in v5.1 訊息 & 檔案 No comment provided by engineer. - - Migrations: %@ + + Migrations: 遷移:%@ No comment provided by engineer. @@ -5162,7 +5152,7 @@ Available in v5.1 Tap to activate profile. - 點擊以激活配置檔案。 + 點擊以激活設定檔。 No comment provided by engineer. @@ -5523,8 +5513,8 @@ It can happen because of some bug or when the connection is compromised.啟用自毀密碼 set passcode view - - Group members can add message reactions. + + Members can add message reactions. 群組內的成員可以新增訊息互動。 No comment provided by engineer. @@ -5699,8 +5689,8 @@ It can happen because of some bug or when the connection is compromised.已移除在 No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. 訊息互動於這個群組內是禁用的。 No comment provided by engineer. @@ -6044,7 +6034,7 @@ It can happen because of some bug or when the connection is compromised. %lld messages marked deleted - %lld 條訊息已刪除 + %lld 則訊息已標記為刪除 Already connecting! @@ -6056,7 +6046,7 @@ It can happen because of some bug or when the connection is compromised. (new) - (新) + (新) %@, %@ and %lld other members connected @@ -6122,6 +6112,374 @@ It can happen because of some bug or when the connection is compromised.Background 後台 + + SimpleX links not allowed + 不允許 SimpleX 連結 + + + Voice messages not allowed + 不允許語音訊息 + + + The text you pasted is not a SimpleX link. + 您貼在這裡的連結不是 SimpleX 連結。 + + + %d file(s) were deleted. + 已刪除 %d 個檔案。 + + + Reset to app theme + 重設至應用程式主題 + + + Retry + 重試 + + + The uploaded database archive will be permanently removed from the servers. + 上傳的資料庫存檔將從伺服器永久移除。 + + + Shape profile images + 塑造個人資料圖片 + + + **Scan / Paste link**: to connect via a link you received. + **掃描/貼上連結**:以透過您收到的連結連線。 + + + Reports + 舉報 + + + Use SOCKS proxy + 使用 SOCKS 代理 + + + Reset all statistics + 重設所有統計數據 + + + SOCKS proxy + SOCKS 代理 + + + Send message to enable calls. + 發送訊息以啟用通話功能。 + + + Send direct message to connect + 直接發送訊息以連結 + + + Scale + 顯示比例 + + + Sent via proxy + 通過代理發送 + + + Servers info + 伺服器訊息 + + + Set message expiration in chats. + 設定聊天中訊息期限。 + + + Share SimpleX address on social media. + 在社交媒體上分享 SimpleX 聯絡地址。 + + + Storage + 存儲 + + + Starting from %@. + 開始於 %@。 + + + The second tick we missed! ✅ + 我們錯過的第二個勾選! ✅ + + + Themes + 主題 + + + %d file(s) failed to download. + %d 個檔案下載失敗。 + + + Session code + 會話代碼 + + + Servers statistics will be reset - this cannot be undone! + 伺服器統計資料將被重設 - 此操作無法撤銷! + + + **Create 1-time link**: to create and share a new invitation link. + **建立一次性連結**:建立並分享新邀請連結。 + + + Set default theme + 設定缺省主題 + + + %lld group events + %lld 個群組事件 + + + Reset all statistics? + 重設所有統計數據? + + + %@ server + %@ 伺服器 + + + %d file(s) were not downloaded. + %d 個檔案未下載。 + + + %d messages not forwarded + %d 則訊息未轉發 + + + Test notifications + 测试通知 + + + (this device v%@) + (此設備 v%@) + + + Settings were changed. + 設定已更改。 + + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + 這操作不能撤銷 - 此聊天中早於所選訊息的收發訊息將被刪除。 + + + Subscription errors + 訂閱錯誤 + + + Report + 舉報 + + + Send messages directly when IP address is protected and your or destination server does not support private routing. + 當 IP 位址受保護且您或目的地伺服器不支援私人路由時,直接傳送訊息。 + + + Reset to user theme + 重設為使用者主題 + + + Use short links (BETA) + 使用短連結(Beta) + + + Up to 100 last messages are sent to new members. + 最多 100 則最後的訊息會傳送至新成員。 + + + %d seconds(s) + %d 秒 + + + %d file(s) are still being downloaded. + 仍在下載 %d 個檔案。 + + + %lld messages blocked by admin + %lld 則訊息被管理員封鎖 + + + Report: %@ + 舉報:%@ + + + Review conditions + 檢視使用條款 + + + Search or paste SimpleX link + 搜尋或貼上 SimpleX 連結 + + + Sent directly + 已直接發送 + + + SimpleX links are prohibited. + 這群組禁止 SimpleX 連結。 + + + Uploaded files + 已上傳的檔案 + + + Use %@ + 使用 %@ + + + Upload errors + 上傳錯誤 + + + Use servers + 使用伺服器 + + + security code changed + 安全碼已變更 + + + These settings are for your current profile **%@**. + 這些設定是針對您目前的設定檔 **%@**。 + + + They can be overridden in contact and group settings. + 您可在連絡人和群組設定中覆寫它們。 + + + %1$@, %2$@ + %1$@, %2$@ + + + Verify connections + 驗證連線 + + + Verify connection + 驗證連線 + + + Verify passphrase + 驗證密碼 + + + Verify code with desktop + 使用桌上電腦驗證代碼 + + + Save list + 儲存列表 + + + Saving %lld messages + 正在儲存 %lld 則訊息 + + + search + 搜尋 + + + requested to connect + 已請求連結 + + + saved + 已儲存 + + + video + 視訊 + + + Tap to Connect + 點擊以連結 + + + Unsupported connection link + 未受支持的連線連結 + + + Saved from + 儲存自 + + + Saved + 已儲存 + + + Scan / Paste link + 掃描/貼上連結 + + + SimpleX + SimpleX + + + Use the app while in the call. + 在通話時使用此應用程式。 + + + v%@ + v%@ + + + Save your profile? + 儲存設定檔? + + + Use for messages + 用於訊息 + + + Uploading archive + 正在上傳檔案庫 + + + Unlink + 從桌上電腦解除連結 + + + %lld messages blocked + 已封鎖 %d 則訊息 + + + The same conditions will apply to operator **%@**. + 相同條件也適用於 **%@** 操作員。 + + + These conditions will also apply for: **%@**. + 這些條件也適用於:**%@**。 + + + Upload failed + 上傳失敗 + + + Use the app with one hand. + 單手使用此應用程式。 + + + Safely receive files + 安全地接收檔案 + + + Saved message + 已儲存的訊息 + + + Use from desktop + 在桌上電腦上使用 + + + Via secure quantum resistant protocol. + 使用量子安全的協定。 + + + Uploaded + 已上傳 +
diff --git a/apps/ios/SimpleX NSE/NSEAPITypes.swift b/apps/ios/SimpleX NSE/NSEAPITypes.swift new file mode 100644 index 0000000000..35a838fff9 --- /dev/null +++ b/apps/ios/SimpleX NSE/NSEAPITypes.swift @@ -0,0 +1,127 @@ +// +// APITypes.swift +// SimpleX +// +// Created by EP on 01/05/2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SimpleXChat + +enum NSEChatCommand: ChatCmdProtocol { + case showActiveUser + case startChat(mainApp: Bool, enableSndFiles: Bool) + case apiActivateChat(restoreChat: Bool) + case apiSuspendChat(timeoutMicroseconds: Int) + case apiSetNetworkConfig(networkConfig: NetCfg) + case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) + case apiSetEncryptLocalFiles(enable: Bool) + case apiGetNtfConns(nonce: String, encNtfInfo: String) + case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) + case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) + case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) + + var cmdString: String { + switch self { + case .showActiveUser: return "/u" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" + case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" + case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" + case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" + case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): + return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" + case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" + case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))" + case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" + case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" + } + } + + private func onOffParam(_ param: String, _ b: Bool?) -> String { + if let b = b { + " \(param)=\(onOff(b))" + } else { + "" + } + } +} + +enum NSEChatResponse: Decodable, ChatAPIResult { + case activeUser(user: User) + case chatStarted + case chatRunning + case rcvFileAccepted(user: UserRef, chatItem: AChatItem) + case ntfConns(ntfConns: [NtfConn]) + case connNtfMessages(receivedMsgs: [RcvNtfMsgInfo]) + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + case cmdOk(user_: UserRef?) + + var responseType: String { + switch self { + case .activeUser: "activeUser" + case .chatStarted: "chatStarted" + case .chatRunning: "chatRunning" + case .rcvFileAccepted: "rcvFileAccepted" + case .ntfConns: "ntfConns" + case .connNtfMessages: "connNtfMessages" + case .ntfMessage: "ntfMessage" + case .cmdOk: "cmdOk" + } + } + + var details: String { + switch self { + case let .activeUser(user): return String(describing: user) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .ntfConns(ntfConns): return String(describing: ntfConns) + case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + case .cmdOk: return noDetails + } + } +} + +enum NSEChatEvent: Decodable, ChatAPIResult { + case chatSuspended + case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case callInvitation(callInvitation: RcvCallInvitation) + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + + var responseType: String { + switch self { + case .chatSuspended: "chatSuspended" + case .contactConnected: "contactConnected" + case .receivedContactRequest: "receivedContactRequest" + case .newChatItems: "newChatItems" + case .rcvFileSndCancelled: "rcvFileSndCancelled" + case .sndFileComplete: "sndFileComplete" + case .sndFileRcvCancelled: "sndFileRcvCancelled" + case .callInvitation: "callInvitation" + case .ntfMessage: "ntfMessage" + } + } + + var details: String { + switch self { + case .chatSuspended: return noDetails + case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) + case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .callInvitation(inv): return String(describing: inv) + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + } + } +} diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 1a2a27ba9b..176da2481e 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -22,18 +22,53 @@ let nseSuspendSchedule: SuspendSchedule = (2, 4) let fastNSESuspendSchedule: SuspendSchedule = (1, 1) -enum NSENotification { - case nse(UNMutableNotificationContent) - case callkit(RcvCallInvitation) - case empty - case msgInfo(NtfMsgInfo) +public enum NSENotificationData { + case connectionEvent(_ user: User, _ connEntity: ConnectionEntity) + case contactConnected(_ user: any UserLike, _ contact: Contact) + case contactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) + case messageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) + case callInvitation(_ invitation: RcvCallInvitation) + case msgInfo(NtfMsgAckInfo) + case noNtf - var isCallInvitation: Bool { + @inline(__always) + var callInvitation: RcvCallInvitation? { switch self { - case let .nse(ntf): ntf.categoryIdentifier == ntfCategoryCallInvitation - case .callkit: true - case .empty: false - case .msgInfo: false + case let .callInvitation(invitation): invitation + default: nil + } + } + + func notificationContent(_ badgeCount: Int) -> UNMutableNotificationContent { + return switch self { + case let .connectionEvent(user, connEntity): createConnectionEventNtf(user, connEntity, badgeCount) + case let .contactConnected(user, contact): createContactConnectedNtf(user, contact, badgeCount) + case let .contactRequest(user, contactRequest): createContactRequestNtf(user, contactRequest, badgeCount) + case let .messageReceived(user, cInfo, cItem): createMessageReceivedNtf(user, cInfo, cItem, badgeCount) + case let .callInvitation(invitation): createCallInvitationNtf(invitation, badgeCount) + case .msgInfo: UNMutableNotificationContent() + case .noNtf: UNMutableNotificationContent() + } + } + + @inline(__always) + var notificationEvent: NSENotificationData? { + switch self { + case .connectionEvent: self + case .contactConnected: self + case .contactRequest: self + case .messageReceived: self + case .callInvitation: self + case .msgInfo: nil + case .noNtf: nil + } + } + + @inline(__always) + var newMsgNtf: NSENotificationData? { + switch self { + case .messageReceived: self + default: nil } } } @@ -43,19 +78,25 @@ enum NSENotification { // or when background notification is received. class NSEThreads { static let shared = NSEThreads() - private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") + private let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") private var allThreads: Set = [] - private var activeThreads: [(UUID, NotificationService)] = [] + private var activeThreads: [(threadId: UUID, nse: NotificationService)] = [] + private var droppedNotifications: [(entityId: ChatId, ntf: NSENotificationData)] = [] + @inline(__always) + private init() {} // only shared instance can be used + + @inline(__always) func newThread() -> UUID { - NSEThreads.queue.sync { + queue.sync { let (_, t) = allThreads.insert(UUID()) return t } } + @inline(__always) func startThread(_ t: UUID, _ service: NotificationService) { - NSEThreads.queue.sync { + queue.sync { if allThreads.contains(t) { activeThreads.append((t, service)) } else { @@ -64,27 +105,111 @@ class NSEThreads { } } - func processNotification(_ id: ChatId, _ ntf: NSENotification) async -> Void { - var waitTime: Int64 = 5_000_000000 - while waitTime > 0 { - if let (_, nse) = rcvEntityThread(id), - nse.shouldProcessNtf && nse.processReceivedNtf(ntf) { - break + // atomically: + // - checks that passed NSE instance can start processing passed notification entity, + // - adds it to the passed NSE instance, + // - marks as started, if no other NSE instance is processing it. + // Making all these steps atomic prevents a race condition between threads when both will be added and none will be started + @inline(__always) + func startEntity(_ nse: NotificationService, _ ntfEntity: NotificationEntity) -> Bool { + queue.sync { + // checking that none of activeThreads with another NSE instance processes the same entity and is not ready + let canStart = !activeThreads.contains(where: { (tId, otherNSE) in + tId != nse.threadId + && otherNSE.notificationEntities.contains(where: { (id, otherEntity) in + id == ntfEntity.entityId + && otherEntity.expectedMsg != nil + }) + }) + // atomically add entity to passed NSE instance + let id = ntfEntity.entityId + nse.notificationEntities[id] = ntfEntity + if canStart { + // and set as started, so it cannot be chosen to start by another NSE entity in nextThread + nse.notificationEntities[id]?.startedProcessingNewMsgs = true + } + return canStart + } + } + + @inline(__always) + func addDroppedNtf(_ id: ChatId, _ ntf: NSENotificationData) { + queue.sync { droppedNotifications.append((id, ntf)) } + } + + // atomically remove and return first dropped notification for the passed entity + @inline(__always) + func takeDroppedNtf(_ ntfEntity: NotificationEntity) -> (entityId: ChatId, ntf: NSENotificationData)? { + queue.sync { + if droppedNotifications.isEmpty { + nil + } else if let i = droppedNotifications.firstIndex(where: { (id, _) in id == ntfEntity.entityId }) { + droppedNotifications.remove(at: i) } else { - try? await Task.sleep(nanoseconds: 10_000000) - waitTime -= 10_000000 + nil } } } - private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? { - NSEThreads.queue.sync { - activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id }) + // passes notification for processing to NSE instance chosen by rcvEntityThread + @inline(__always) + func processNotification(_ id: ChatId, _ ntf: NSENotificationData) async -> Void { + if let (nse, ntfEntity, expectedMsg) = rcvEntityThread(id, ntf) { + logger.debug("NotificationService processNotification \(id): found nse thread expecting message") + if nse.processReceivedNtf(ntfEntity, expectedMsg, ntf) { + nse.finalizeEntity(id) + } } } + // atomically: + // - chooses active NSE instance that is ready to process notifications and expects message for passed entity ID + // - returns all dependencies for processing (notification entity and expected message) + // - adds notification to droppedNotifications if no ready NSE instance is found for the entity + @inline(__always) + private func rcvEntityThread(_ id: ChatId, _ ntf: NSENotificationData) -> (NotificationService, NotificationEntity, NtfMsgInfo)? { + queue.sync { + // this selects the earliest thread that: + // 1) has this connection entity in nse.notificationEntitites + // 2) has not completed processing messages for this connection entity (not ready) + let r = activeThreads.lazy.compactMap({ (_, nse) in + let ntfEntity = nse.notificationEntities[id] + return if let ntfEntity, let expectedMsg = ntfEntity.expectedMsg, ntfEntity.shouldProcessNtf { + (nse, ntfEntity, expectedMsg) + } else { + nil + } + }).first + if r == nil { droppedNotifications.append((id, ntf)) } + return r + } + } + + // Atomically mark entity in the passed NSE instance as not expecting messages, + // and signal the next NSE instance with this entity to start its processing. + @inline(__always) + func signalNextThread(_ nse: NotificationService, _ id: ChatId) { + queue.sync { + nse.notificationEntities[id]?.expectedMsg = nil + nse.notificationEntities[id]?.shouldProcessNtf = false + let next = activeThreads.first(where: { (_, nseNext) in + if let ntfEntity = nseNext.notificationEntities[id] { + ntfEntity.expectedMsg != nil && !ntfEntity.startedProcessingNewMsgs + } else { + false + } + }) + if let (tNext, nseNext) = next { + if let t = nse.threadId { logger.debug("NotificationService thread \(t): signalNextThread: signal next thread \(tNext) for entity \(id)") } + nseNext.notificationEntities[id]?.startedProcessingNewMsgs = true + nseNext.notificationEntities[id]?.semaphore.signal() + } + } + } + + @inline(__always) func endThread(_ t: UUID) -> Bool { - NSEThreads.queue.sync { + queue.sync { let tActive: UUID? = if let index = activeThreads.firstIndex(where: { $0.0 == t }) { activeThreads.remove(at: index).0 } else { @@ -101,53 +226,102 @@ class NSEThreads { } } + @inline(__always) var noThreads: Bool { allThreads.isEmpty } } +// NotificationEntity is a processing state for notifications from a single connection entity (message queue). +// Each NSE instance within NSE process can have more than one NotificationEntity. +// NotificationEntities of an NSE instance are processed concurrently, as messages arrive in any order. +// NotificationEntities for the same connection across multiple NSE instances (NSEThreads) are processed sequentially, so that the earliest NSE instance receives the earliest messages. +// The reason for this complexity is to process all required messages within allotted 30 seconds, +// accounting for the possibility that multiple notifications may be delivered concurrently. +struct NotificationEntity { + var ntfConn: NtfConn + var entityId: ChatId + + // expectedMsg == nil means that entity already has the best attempt to deliver, and no more messages are expected. + // It happens when: + // - the user is muted (set to nil in mkNotificationEntity) + // - apiGetNtfConns returns that there are no new messages (msgId in notification matches previously received), + // - messaging server fails to respond or replies that there are no messages (apiGetConnNtfMessages / getConnNtfMessage), + // - the message is received with the correct ID or timestamp (set to nil in signalNextThread). + var expectedMsg: NtfMsgInfo? + var allowedGetNextAttempts: Int = 3 + var msgBestAttemptNtf: NSENotificationData + + // startedProcessingNewMsgs determines that the entity stared processing events once it processed dropped notifications. + // It remains true when shouldProcessNtf is set to false, to prevent NSE from being chosen as the next for the entity. + // It is atomically set to true by startThead or by nextThread + var startedProcessingNewMsgs: Bool = false + + // shouldProcessNtf determines that NSE should process events for this entity, + // it is atomically set: + // - to true in processDroppedNotifications in case dropped notification is not chosen for delivery, and more messages are needed. + // - to false in nextThread + var shouldProcessNtf: Bool = false + + // this semaphone is used to wait for another NSE instance processing events for the same entity + var semaphore: DispatchSemaphore = DispatchSemaphore(value: 0) + + var connMsgReq: ConnMsgReq? { + if let expectedMsg { + ConnMsgReq(msgConnId: ntfConn.agentConnId, msgDbQueueId: ntfConn.agentDbQueueId, msgTs: expectedMsg.msgTs) + } else { + nil + } + } +} + // Notification service extension creates a new instance of the class and calls didReceive for each notification. // Each didReceive is called in its own thread, but multiple calls can be made in one process, and, empirically, there is never // more than one process of notification service extension exists at a time. // Soon after notification service delivers the last notification it is either suspended or terminated. class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? - var bestAttemptNtf: NSENotification? + // served as notification if no message attempts (msgBestAttemptNtf) could be produced + var serviceBestAttemptNtf: UNMutableNotificationContent? var badgeCount: Int = 0 // thread is added to allThreads here - if thread did not start chat, // chat does not need to be suspended but NSE state still needs to be set to "suspended". var threadId: UUID? = NSEThreads.shared.newThread() - var notificationInfo: NtfMessages? - var receiveEntityId: String? - var expectedMessage: String? - // return true if the message is taken - it prevents sending it to another NotificationService instance for processing - var shouldProcessNtf = false + var notificationEntities: Dictionary = [:] // key is entityId var appSubscriber: AppSubscriber? var returnedSuspension = false override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { logger.debug("DEBUGGING: NotificationService.didReceive") - let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() } - setBestAttemptNtf(ntf) + let receivedNtf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() } + setServiceBestAttemptNtf(receivedNtf) self.contentHandler = contentHandler registerGroupDefaults() let appState = appStateGroupDefault.get() logger.debug("NotificationService: app is \(appState.rawValue)") switch appState { case .stopped: +// Use this block to debug notificaitons delivery in CLI, with "ejected" database and stopped chat +// if let nrData = ntfRequestData(request) { +// logger.debug("NotificationService get notification connections: /_ntf conns \(nrData.nonce) \(nrData.encNtfInfo)") +// contentHandler(receivedNtf) +// return; +// } setBadgeCount() - setBestAttemptNtf(createAppStoppedNtf()) - deliverBestAttemptNtf() + contentHandler(createAppStoppedNtf(badgeCount)) case .suspended: - setBadgeCount() - receiveNtfMessages(request, contentHandler) + setExpirationTimer() + receiveNtfMessages(request) case .suspending: - setBadgeCount() + // while application is suspending, the current instance will be waiting + setExpirationTimer() Task { let state: AppState = await withCheckedContinuation { cont in + // this subscriber uses message delivery via NSFileCoordinator to communicate between the app and NSE appSubscriber = appStateSubscriber { s in if s == .suspended { appSuspension(s) } } + // this is a fallback timeout, in case message from the app does not arrive DispatchQueue.global().asyncAfter(deadline: .now() + Double(appSuspendTimeout) + 1) { logger.debug("NotificationService: appSuspension timeout") appSuspension(appStateGroupDefault.get()) @@ -163,89 +337,250 @@ class NotificationService: UNNotificationServiceExtension { } } logger.debug("NotificationService: app state is now \(state.rawValue)") - if state.inactive { - receiveNtfMessages(request, contentHandler) + if state.inactive && self.contentHandler != nil { + receiveNtfMessages(request) } else { - deliverBestAttemptNtf() + contentHandler(receivedNtf) } } - default: - deliverBestAttemptNtf() + case .active: contentHandler(receivedNtf) + case .activating: contentHandler(receivedNtf) + case .bgRefresh: contentHandler(receivedNtf) } } - func receiveNtfMessages(_ request: UNNotificationRequest, _ contentHandler: @escaping (UNNotificationContent) -> Void) { + // This timer compensates for the scenarios when serviceExtensionTimeWillExpire does not fire at all. + // It is not clear why in some cases it does not fire, possibly it is a bug, + // or it depends on what the current thread is doing at the moment. + // If notification is not delivered and not cancelled, no further notifications will be processed. + @inline(__always) + private func setExpirationTimer() -> Void { + DispatchQueue.main.asyncAfter(deadline: .now() + 30) { + self.deliverBestAttemptNtf(urgent: true) + } + } + + @inline(__always) + private func ntfRequestData(_ request: UNNotificationRequest) -> (nonce: String, encNtfInfo: String)? { + if let ntfData = request.content.userInfo["notificationData"] as? [AnyHashable : Any], + let nonce = ntfData["nonce"] as? String, + let encNtfInfo = ntfData["message"] as? String { + (nonce, encNtfInfo) + } else { + nil + } + } + + // This function triggers notification message delivery for connection entities referenced in the notification. + // Notification may reference multiple connection entities (message queues) in order to compensate for Apple servers + // only delivering the latest notification, so it allows receiving messages from up to 6 contacts and groups from a + // single notification. This aggregation is handled by a notification server and is delivered via APNS servers in + // e2e encrypted envelope, and the app core prevents duplicate processing by keeping track of the last processed message. + + // The process steps: + // 0. apiGetConnNtfMessages or getConnNtfMessage get messages from the server for passed connection entities. + // We don't know in advance which chat events will be delivered from app core for a given notification, + // it may be a message, but it can also be contact request, various protocol confirmations, calls, etc., + // this function only returns metadata for the expected chat events. + // This metadata is correlated with .ntfMessage core event / .msgInfo notification marker - + // this marker allows determining when some message completed processing. + // 1. receiveMessages: singleton loop receiving events from core. + // 2. receivedMsgNtf: maps core events to notification events. + // 3. NSEThreads.shared.processNotification: chooses which notification service instance in the current process should process notification. + // While most of the time we observe that notifications are delivered sequentially, nothing in the documentation confirms it is sequential, + // and from various sources it follows that each instance executes in its own thread, so concurrency is expected. + // 4. processReceivedNtf: one of the instances of NSE processes notification event, deciding whether to request further messages + // for a given connection entity (via getConnNtfMessage) or that the correct message was received and notification can be delivered (deliverBestAttemptNtf). + // It is based on .msgInfo markers that indicate that message with a given timestamp was processed. + // 5. deliverBestAttemptNtf: is called multiple times, once each connection receives enough messages (based on .msgInfo marker). + // If further messages are expected, this function does nothing (unless it is called with urgent flag from timeout/expiration handlers). + func receiveNtfMessages(_ request: UNNotificationRequest) { logger.debug("NotificationService: receiveNtfMessages") if case .documents = dbContainerGroupDefault.get() { deliverBestAttemptNtf() return } - let userInfo = request.content.userInfo - if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any], - let nonce = ntfData["nonce"] as? String, - let encNtfInfo = ntfData["message"] as? String, - // check it here again + if let nrData = ntfRequestData(request), + // Check that the app is still inactive before starting the core. appStateGroupDefault.get().inactive { // thread is added to activeThreads tracking set here - if thread started chat it needs to be suspended - if let t = threadId { NSEThreads.shared.startThread(t, self) } + guard let t = threadId else { return } + NSEThreads.shared.startThread(t, self) let dbStatus = startChat() + // If database is opened successfully, get the list of connection entities (group members, contacts) + // that are referenced in the encrypted notification metadata. if case .ok = dbStatus, - let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) { - logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessage_ == nil ? 0 : 1))") - if let connEntity = ntfInfo.connEntity_ { - setBestAttemptNtf( - ntfInfo.ntfsEnabled - ? .nse(createConnectionEventNtf(ntfInfo.user, connEntity)) - : .empty - ) - if let id = connEntity.id, ntfInfo.msgTs != nil { - notificationInfo = ntfInfo - receiveEntityId = id - expectedMessage = ntfInfo.ntfMessage_.flatMap { $0.msgId } - shouldProcessNtf = true - return + let ntfConns = apiGetNtfConns(nonce: nrData.nonce, encNtfInfo: nrData.encNtfInfo) { + logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \(ntfConns.count)") + // uncomment localDisplayName in ConnectionEntity + // logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns \(String(describing: ntfConns.map { $0.connEntity.localDisplayName }))") + + // Prepare expected messages - they will be delivered to the reception loop in this chain: + // They are atomically added to the instance notificationEntities inside msgReqs loop, to avoid any race conditions. + let ntfEntities = ntfConns.compactMap(mkNotificationEntity) + + // collect notification message requests for all connection entities + let msgReqs: [(chatId: String, connMsgReq: ConnMsgReq)] = ntfEntities.compactMap { ntfEntity -> (chatId: String, connMsgReq: ConnMsgReq)? in + // No need to request messages for connection entities that are "ready", + // e.g. for muted users or when the message is not expected based on notification. + let id = ntfEntity.entityId + if let expectedMsg = ntfEntity.expectedMsg { + if NSEThreads.shared.startEntity(self, ntfEntity) { // atomically checks and adds ntfEntity to NSE + // process any notifications "postponed" by the previous instance + let completed = processDroppedNotifications(ntfEntity, expectedMsg) + return if !completed, let connMsgReq = notificationEntities[id]?.connMsgReq { + (id, connMsgReq) + } else { + nil + } + } else { + // wait for another instance processing the same connection entity + logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) waiting on semaphore") + // this semaphore will be released by signalNextThread function, that looks up the instance + // waiting for the connection entity via activeThreads in NSEThreads + notificationEntities[id]?.semaphore.wait() + logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) proceeding after semaphore") + Task { + // process any notifications "postponed" by the previous instance + let completed = processDroppedNotifications(ntfEntity, expectedMsg) + // Request messages from the server for this connection entity. + // It triggers event delivery to receiveMessages loop (see above). + if !completed, let connMsgReq = notificationEntities[id]?.connMsgReq, + let rcvMsg = getConnNtfMessage(connMsgReq: connMsgReq), + rcvMsg.noMsg { + // if server returns error or "no message", deliver what we have for this connection entity. + finalizeEntity(id) // also releases any waiting threads for this entity + } + } + return nil + } + } else { // no expected message + notificationEntities[id] = ntfEntity + return nil + } + } + + // Request messages for all connection entities that were not used by other instances. + // It triggers event delivery to receiveMessages loop (see above). + if !msgReqs.isEmpty, + let rcvMsgs = apiGetConnNtfMessages(connMsgReqs: msgReqs.map { $0.connMsgReq }) { + for i in 0 ..< min(msgReqs.count, rcvMsgs.count) { // a sanity check, API always returns the same size + if rcvMsgs[i].noMsg { + // mark entity as ready if there are no message on the server (or on error) + finalizeEntity(msgReqs[i].chatId) + } } } } else if let dbStatus = dbStatus { - setBestAttemptNtf(createErrorNtf(dbStatus)) + setServiceBestAttemptNtf(createErrorNtf(dbStatus, badgeCount)) } } + // try to deliver the best attempt before exiting deliverBestAttemptNtf() } + @inline(__always) + func mkNotificationEntity(ntfConn: NtfConn) -> NotificationEntity? { + if let rcvEntityId = ntfConn.connEntity.id { + // don't receive messages for muted user profile + let expectedMsg: NtfMsgInfo? = if ntfConn.user.showNotifications { ntfConn.expectedMsg_ } else { nil } + return NotificationEntity( + ntfConn: ntfConn, + entityId: rcvEntityId, + expectedMsg: expectedMsg, + msgBestAttemptNtf: defaultBestAttemptNtf(ntfConn) + ) + } + return nil + } + + // Processes notifications received and postponed by the previous NSE instance + func processDroppedNotifications(_ ntfEntity: NotificationEntity, _ expectedMsg: NtfMsgInfo) -> Bool { + var completed = false + while !completed { + if let dropped = NSEThreads.shared.takeDroppedNtf(ntfEntity) { + completed = processReceivedNtf(ntfEntity, expectedMsg, dropped.ntf) + } else { + break + } + } + if completed { + finalizeEntity(ntfEntity.entityId) + } else { + notificationEntities[ntfEntity.entityId]?.shouldProcessNtf = true + } + return completed + } + override func serviceExtensionTimeWillExpire() { logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire") deliverBestAttemptNtf(urgent: true) } - func processReceivedNtf(_ ntf: NSENotification) -> Bool { - guard let ntfInfo = notificationInfo, let msgTs = ntfInfo.msgTs else { return false } - if !ntfInfo.user.showNotifications { - self.setBestAttemptNtf(.empty) - } + @inline(__always) + var expectingMoreMessages: Bool { + notificationEntities.contains { $0.value.expectedMsg != nil } + } + + // processReceivedNtf returns "completed" - true when no more messages for the passed entity should be processed by the current NSE instance. + // This is used to call finalizeEntity(id) and by processDroppedNotifications to decide if further processing is needed. + func processReceivedNtf(_ ntfEntity: NotificationEntity, _ expectedMsg: NtfMsgInfo, _ ntf: NSENotificationData) -> Bool { + let id = ntfEntity.entityId if case let .msgInfo(info) = ntf { - if info.msgId == expectedMessage { - expectedMessage = nil - logger.debug("NotificationService processNtf: msgInfo") - self.deliverBestAttemptNtf() + if info.msgId == expectedMsg.msgId { + // The message for this instance is processed, no more expected, deliver. + logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): expected") return true - } else if info.msgTs > msgTs { - logger.debug("NotificationService processNtf: unexpected msgInfo, let other instance to process it, stopping this one") - self.deliverBestAttemptNtf() - return false + } else if let msgTs = info.msgTs_, msgTs > expectedMsg.msgTs { + // Otherwise check timestamp - if it is after the currently expected timestamp, preserve .msgInfo marker for the next instance. + logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, let other instance to process it, stopping this one") + NSEThreads.shared.addDroppedNtf(id, ntf) + return true + } else if ntfEntity.allowedGetNextAttempts > 0, let connMsgReq = ntfEntity.connMsgReq { + // Otherwise this instance expects more messages, and still has allowed attempts - + // request more messages with getConnNtfMessage. + logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message") + notificationEntities[id]?.allowedGetNextAttempts -= 1 + let receivedMsg = getConnNtfMessage(connMsgReq: connMsgReq) + if case let .info(msg) = receivedMsg, let msg { + // Server delivered message, it will be processed in the loop - see the comments in receiveNtfMessages. + logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(msg.msgId, privacy: .private)") + return false + } else { + // Server reported no messages or error, deliver what we have. + logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt") + return true + } } else { - logger.debug("NotificationService processNtf: unknown message, let other instance to process it") - return false + // Current instance needs more messages, but ran out of attempts - deliver what we have. + logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unknown message, let other instance to process it") + return true } - } else if ntfInfo.user.showNotifications { + } else if ntfEntity.ntfConn.user.showNotifications { + // This is the notification event for the user with enabled notifications. logger.debug("NotificationService processNtf: setting best attempt") - self.setBestAttemptNtf(ntf) - if ntf.isCallInvitation { - self.deliverBestAttemptNtf() + if ntf.notificationEvent != nil { + setBadgeCount() } + // If previous "best attempt" is not a call, or if the current notification is a call, replace best attempt. + // NOTE: we are delaying it until notification marker to make sure we are not delivering stale calls that can't be connected. + // A better logic could be to check whether we have a call in the best attempt while processing .msgInfo marker above. + // If the best attempt is a call, and its marker is received, and the call is recent (e.g., the last 30 seconds), it would deliver at once, + // instead of requesting further messages. + if ntfEntity.msgBestAttemptNtf.callInvitation == nil || ntf.callInvitation != nil { + notificationEntities[id]?.msgBestAttemptNtf = ntf + } // otherwise keep call as best attempt + return false + } else { + // We should not get to this branch, as notifications are not delivered for muted users. return true } - return false + } + + func finalizeEntity(_ entityId: ChatId) { + if let t = threadId { logger.debug("NotificationService thread \(t): entityReady: entity \(entityId)") } + NSEThreads.shared.signalNextThread(self, entityId) + deliverBestAttemptNtf() } func setBadgeCount() { @@ -253,51 +588,53 @@ class NotificationService: UNNotificationServiceExtension { ntfBadgeCountGroupDefault.set(badgeCount) } - func setBestAttemptNtf(_ ntf: UNMutableNotificationContent) { - setBestAttemptNtf(.nse(ntf)) - } - - func setBestAttemptNtf(_ ntf: NSENotification) { - logger.debug("NotificationService.setBestAttemptNtf") - if case let .nse(notification) = ntf { - notification.badge = badgeCount as NSNumber - bestAttemptNtf = .nse(notification) - } else { - bestAttemptNtf = ntf - } + @inline(__always) + func setServiceBestAttemptNtf(_ ntf: UNMutableNotificationContent) { + logger.debug("NotificationService.setServiceBestAttemptNtf") + serviceBestAttemptNtf = ntf } private func deliverBestAttemptNtf(urgent: Bool = false) { - logger.debug("NotificationService.deliverBestAttemptNtf") - // stop processing other messages - shouldProcessNtf = false + logger.debug("NotificationService.deliverBestAttemptNtf urgent: \(urgent) expectingMoreMessages: \(self.expectingMoreMessages)") + if let handler = contentHandler, urgent || !expectingMoreMessages { + if urgent { + contentHandler = nil + } + logger.debug("NotificationService.deliverBestAttemptNtf") + // stop processing other messages + for (key, _) in notificationEntities { + notificationEntities[key]?.shouldProcessNtf = false + } - let suspend: Bool - if let t = threadId { - threadId = nil - suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads - } else { - suspend = false + let suspend: Bool + if let t = threadId { + threadId = nil + suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads + } else { + suspend = false + } + deliverCallkitOrNotification(urgent: urgent, suspend: suspend, handler: handler) } - deliverCallkitOrNotification(urgent: urgent, suspend: suspend) } - private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) { - if case .callkit = bestAttemptNtf { + @inline(__always) + private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false, handler: @escaping (UNNotificationContent) -> Void) { + let callInv = notificationEntities.lazy.compactMap({ $0.value.msgBestAttemptNtf.callInvitation }).first + if callInv != nil && useCallKit() { logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit") + // suspending NSE even though there may be other notifications + // to allow the app to process callkit call if urgent { - // suspending NSE even though there may be other notifications - // to allow the app to process callkit call suspendChat(0) - deliverNotification() + deliverNotification(handler, callInv) } else { - // suspending NSE with delay and delivering after the suspension + // when not "urgent", suspending NSE with delay and delivering after the suspension // because pushkit notification must be processed without delay - // to avoid app termination + // to avoid app termination. DispatchQueue.global().asyncAfter(deadline: .now() + fastNSESuspendSchedule.delay) { suspendChat(fastNSESuspendSchedule.timeout) DispatchQueue.global().asyncAfter(deadline: .now() + Double(fastNSESuspendSchedule.timeout)) { - self.deliverNotification() + self.deliverNotification(handler, callInv) } } } @@ -316,38 +653,119 @@ class NotificationService: UNNotificationServiceExtension { } } } - deliverNotification() + deliverNotification(handler, callInv) } } - private func deliverNotification() { - if let handler = contentHandler, let ntf = bestAttemptNtf { + private func deliverNotification(_ handler: @escaping (UNNotificationContent) -> Void, _ callInv: RcvCallInvitation?) { + if let serviceNtf = serviceBestAttemptNtf { + serviceBestAttemptNtf = nil contentHandler = nil - bestAttemptNtf = nil - let deliver: (UNMutableNotificationContent?) -> Void = { ntf in - let useNtf = if let ntf = ntf { - appStateGroupDefault.get().running ? UNMutableNotificationContent() : ntf + if let callInv { + if useCallKit() { + logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(callInv.contact.id)") + CXProvider.reportNewIncomingVoIPPushPayload([ + "displayName": callInv.contact.displayName, + "contactId": callInv.contact.id, + "callUUID": callInv.callUUID ?? "", + "media": callInv.callType.media.rawValue, + "callTs": callInv.callTs.timeIntervalSince1970 + ]) { error in + logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") + handler(error == nil ? UNMutableNotificationContent() : createCallInvitationNtf(callInv, self.badgeCount)) + } } else { - UNMutableNotificationContent() + handler(createCallInvitationNtf(callInv, badgeCount)) } - handler(useNtf) + } else if notificationEntities.isEmpty { + handler(serviceNtf) + } else { + handler(prepareNotification()) } + } + } + + @inline(__always) + private func prepareNotification() -> UNMutableNotificationContent { + // uncomment localDisplayName in ConnectionEntity + // let conns = self.notificationEntities.compactMap { $0.value.ntfConn.connEntity.localDisplayName } + // logger.debug("NotificationService prepareNotification for \(String(describing: conns))") + let ntfs = notificationEntities.compactMap { $0.value.msgBestAttemptNtf.notificationEvent } + let newMsgNtfs = ntfs.compactMap({ $0.newMsgNtf }) + let useNtfs = if newMsgNtfs.isEmpty { ntfs } else { newMsgNtfs } + return createNtf(useNtfs) + + func createNtf(_ ntfs: [NSENotificationData]) -> UNMutableNotificationContent { + logger.debug("NotificationService prepareNotification: \(ntfs.count) events") + return switch ntfs.count { + case 0: UNMutableNotificationContent() // used to mute notifications that did not unsubscribe yet + case 1: ntfs[0].notificationContent(badgeCount) + default: createJointNtf(ntfs) + } + } + } + + // NOTE: this can be improved when there are two or more connection entity events when no messages were delivered. + // Possibly, it is better to postpone this improvement until message priority is added to prevent notifications in muted groups, + // unless it is a mention, a reply or some other high priority message marked for notification delivery. + @inline(__always) + private func createJointNtf(_ ntfs: [NSENotificationData]) -> UNMutableNotificationContent { + let previewMode = ntfPreviewModeGroupDefault.get() + logger.debug("NotificationService.createJointNtf ntfs: \(ntfs.count)") + let (userId, chatsNames) = newMsgsChatsNames(ntfs) + if !chatsNames.isEmpty, let userId { + let body = if previewMode == .hidden { + String.localizedStringWithFormat(NSLocalizedString("From %d chat(s)", comment: "notification body"), chatsNames.count) + } else { + String.localizedStringWithFormat(NSLocalizedString("From: %@", comment: "notification body"), newMsgsChatsNamesStr(chatsNames)) + } + return createNotification( + categoryIdentifier: ntfCategoryManyEvents, + title: NSLocalizedString("New messages", comment: "notification"), + body: body, + userInfo: ["userId": userId], + badgeCount: badgeCount + ) + } else { + return createNotification( + categoryIdentifier: ntfCategoryManyEvents, + title: NSLocalizedString("New events", comment: "notification"), + body: String.localizedStringWithFormat(NSLocalizedString("%d new events", comment: "notification body"), ntfs.count), + badgeCount: badgeCount + ) + } + } + + @inline(__always) + private func newMsgsChatsNames(_ ntfs: [NSENotificationData]) -> (Int64?, [String]) { + var seenChatIds = Set() + var chatsNames: [String] = [] + var userId: Int64? + for ntf in ntfs { switch ntf { - case let .nse(content): deliver(content) - case let .callkit(invitation): - logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)") - CXProvider.reportNewIncomingVoIPPushPayload([ - "displayName": invitation.contact.displayName, - "contactId": invitation.contact.id, - "media": invitation.callType.media.rawValue - ]) { error in - logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") - deliver(error == nil ? nil : createCallInvitationNtf(invitation)) + case let .messageReceived(user, chat, _): + if seenChatIds.isEmpty { userId = user.userId } + if !seenChatIds.contains(chat.id) { + seenChatIds.insert(chat.id) + chatsNames.append(chat.chatViewName) } - case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet - case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo + default: () } } + return (userId, chatsNames) + } + + @inline(__always) + private func newMsgsChatsNamesStr(_ names: [String]) -> String { + return switch names.count { + case 1: names[0] + case 2: "\(names[0]) and \(names[1])" + case 3: "\(names[0] + ", " + names[1]) and \(names[2])" + default: + names.count > 3 + ? "\(names[0]), \(names[1]) and \(names.count - 2) other chats" + : "" + } } } @@ -356,9 +774,8 @@ class NSEChatState { static let shared = NSEChatState() private var value_ = NSEState.created - var value: NSEState { - value_ - } + @inline(__always) + var value: NSEState { value_ } func set(_ state: NSEState) { nseStateGroupDefault.set(state) @@ -366,7 +783,7 @@ class NSEChatState { value_ = state } - init() { + private init() { // This is always set to .created state, as in case previous start of NSE crashed in .active state, it is stored correctly. // Otherwise the app will be activating slower set(.created) @@ -414,7 +831,7 @@ func startChat() -> DBMigrationResult? { startLock.wait() defer { startLock.signal() } - + if hasChatCtrl() { return switch NSEChatState.shared.value { case .created: doStartChat() @@ -444,7 +861,7 @@ func doStartChat() -> DBMigrationResult? { let state = NSEChatState.shared.value NSEChatState.shared.set(.starting) if let user = apiGetActiveUser() { - logger.debug("NotificationService active user \(String(describing: user))") + logger.debug("NotificationService active user \(user.displayName)") do { try setNetworkConfig(networkConfig) try apiSetAppFilePaths(filesFolder: getAppFilesDirectory().path, tempFolder: getTempFilesDirectory().path, assetsFolder: getWallpaperDirectory().deletingLastPathComponent().path) @@ -537,12 +954,18 @@ func receiveMessages() async { } func receiveMsg() async { - if let msg = await chatRecvMsg() { + switch await chatRecvMsg() { + case let .result(msg): logger.debug("NotificationService receiveMsg: message") if let (id, ntf) = await receivedMsgNtf(msg) { logger.debug("NotificationService receiveMsg: notification") await NSEThreads.shared.processNotification(id, ntf) } + case let .error(err): + logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))") + case let .invalid(type, _): + logger.error("NotificationService receivedMsgNtf invalid: \(type)") + case .none: () } } @@ -552,36 +975,46 @@ func receiveMessages() async { } } -func chatRecvMsg() async -> ChatResponse? { +func chatRecvMsg() async -> APIResult? { await withCheckedContinuation { cont in - let resp = recvSimpleXMsg() + let resp: APIResult? = recvSimpleXMsg() cont.resume(returning: resp) } } private let isInChina = SKStorefront().countryCode == "CHN" + +@inline(__always) private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } -func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { +@inline(__always) +func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? { logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") switch res { case let .contactConnected(user, contact, _): - return (contact.id, .nse(createContactConnectedNtf(user, contact))) + return (contact.id, .contactConnected(user, contact)) // case let .contactConnecting(contact): // TODO profile update case let .receivedContactRequest(user, contactRequest): - return (UserContact(contactRequest: contactRequest).id, .nse(createContactRequestNtf(user, contactRequest))) - case let .newChatItem(user, aChatItem): - let cInfo = aChatItem.chatInfo - var cItem = aChatItem.chatItem - if !cInfo.ntfsEnabled { - ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) + return (UserContact(contactRequest: contactRequest).id, .contactRequest(user, contactRequest)) + case let .newChatItems(user, chatItems): + // Received items are created one at a time + if let chatItem = chatItems.first { + let cInfo = chatItem.chatInfo + var cItem = chatItem.chatItem + if let file = cItem.autoReceiveFile() { + cItem = autoReceiveFile(file) ?? cItem + } + let ntf: NSENotificationData = (cInfo.ntfsEnabled(chatItem: cItem) && cItem.showNotification) ? .messageReceived(user, cInfo, cItem) : .noNtf + let chatIdOrMemberId = if case let .groupRcv(groupMember) = chatItem.chatItem.chatDir { + groupMember.id + } else { + chatItem.chatInfo.id + } + return (chatIdOrMemberId, ntf) + } else { + return nil } - if let file = cItem.autoReceiveFile() { - cItem = autoReceiveFile(file) ?? cItem - } - let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty - return cItem.showNotification ? (aChatItem.chatId, ntf) : nil case let .rcvFileSndCancelled(_, aChatItem, _): cleanupFile(aChatItem) return nil @@ -595,24 +1028,16 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { return nil case let .callInvitation(invitation): // Do not post it without CallKit support, iOS will stop launching the app without showing CallKit - return ( - invitation.contact.id, - useCallKit() ? .callkit(invitation) : .nse(createCallInvitationNtf(invitation)) - ) + return (invitation.contact.id, .callInvitation(invitation)) case let .ntfMessage(_, connEntity, ntfMessage): return if let id = connEntity.id { (id, .msgInfo(ntfMessage)) } else { nil } case .chatSuspended: chatSuspended() return nil - case let .chatError(_, err): - logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))") - return nil - default: - logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)") - return nil } } +@inline(__always) func updateNetCfg() { let newNetConfig = getNetCfg() if newNetConfig != networkConfig { @@ -627,14 +1052,14 @@ func updateNetCfg() { } func apiGetActiveUser() -> User? { - let r = sendSimpleXCmd(.showActiveUser) + let r: APIResult = sendSimpleXCmd(NSEChatCommand.showActiveUser) logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)") switch r { - case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): + case let .result(.activeUser(user)): return user + case .error(.error(.noActiveUser)): logger.debug("apiGetActiveUser sendSimpleXCmd no active user") return nil - case let .chatCmdError(_, err): + case let .error(err): logger.debug("apiGetActiveUser sendSimpleXCmd error: \(String(describing: err))") return nil default: @@ -644,71 +1069,93 @@ func apiGetActiveUser() -> User? { } func apiStartChat() throws -> Bool { - let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: false)) + let r: APIResult = sendSimpleXCmd(NSEChatCommand.startChat(mainApp: false, enableSndFiles: false)) switch r { - case .chatStarted: return true - case .chatRunning: return false - default: throw r + case .result(.chatStarted): return true + case .result(.chatRunning): return false + default: throw r.unexpected } } func apiActivateChat() -> Bool { chatReopenStore() - let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false)) - if case .cmdOk = r { return true } + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiActivateChat(restoreChat: false)) + if case .result(.cmdOk) = r { return true } logger.error("NotificationService apiActivateChat error: \(String(describing: r))") return false } func apiSuspendChat(timeoutMicroseconds: Int) -> Bool { - let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) - if case .cmdOk = r { return true } + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) + if case .result(.cmdOk) = r { return true } logger.error("NotificationService apiSuspendChat error: \(String(describing: r))") return false } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws { - let r = sendSimpleXCmd(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSetEncryptLocalFiles(enable: enable)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } -func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { +func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? { guard apiGetActiveUser() != nil else { - logger.debug("no active user") + logger.debug("NotificationService: no active user") return nil } - let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo)) - if case let .ntfMessages(user, connEntity_, msgTs, ntfMessage_) = r, let user = user { - logger.debug("apiGetNtfMessage response ntfMessages: \(ntfMessage_ == nil ? 0 : 1)") - return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessage_: ntfMessage_) - } else if case let .chatCmdError(_, error) = r { - logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) + if case let .result(.ntfConns(ntfConns)) = r { + logger.debug("NotificationService apiGetNtfConns response ntfConns: \(ntfConns.count) conections") + return ntfConns + } else if case let .error(error) = r { + logger.debug("NotificationService apiGetNtfMessage error response: \(String.init(describing: error))") } else { - logger.debug("apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))") + logger.debug("NotificationService apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))") } return nil } +func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [RcvNtfMsgInfo]? { + guard apiGetActiveUser() != nil else { + logger.debug("no active user") + return nil + } +// logger.debug("NotificationService apiGetConnNtfMessages command: \(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") + logger.debug("NotificationService apiGetConnNtfMessages requests: \(connMsgReqs.count)") + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) + if case let .result(.connNtfMessages(msgs)) = r { +// logger.debug("NotificationService apiGetConnNtfMessages responses: \(String(describing: msgs))") + logger.debug("NotificationService apiGetConnNtfMessages responses: total \(msgs.count), expecting messages \(msgs.count { !$0.noMsg }), errors \(msgs.count { $0.isError })") + return msgs + } + logger.debug("NotificationService apiGetConnNtfMessages error: \(responseError(r.unexpected))") + return nil +} + +func getConnNtfMessage(connMsgReq: ConnMsgReq) -> RcvNtfMsgInfo? { + let r = apiGetConnNtfMessages(connMsgReqs: [connMsgReq]) + return if let r, r.count > 0 { r[0] } else { nil } +} + func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? { let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() - let r = sendSimpleXCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) - if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } - logger.error("receiveFile error: \(responseError(r))") + let r: APIResult = sendSimpleXCmd(NSEChatCommand.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) + if case let .result(.rcvFileAccepted(_, chatItem)) = r { return chatItem } + logger.error("receiveFile error: \(responseError(r.unexpected))") return nil } func apiSetFileToReceive(fileId: Int64, encrypted: Bool) { let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() - let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) - if case .cmdOk = r { return } - logger.error("setFileToReceive error: \(responseError(r))") + let r: APIResult = sendSimpleXCmd(NSEChatCommand.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) + if case .result(.cmdOk) = r { return } + logger.error("setFileToReceive error: \(responseError(r.unexpected))") } func autoReceiveFile(_ file: CIFile) -> ChatItem? { @@ -725,18 +1172,32 @@ func autoReceiveFile(_ file: CIFile) -> ChatItem? { } func setNetworkConfig(_ cfg: NetCfg) throws { - let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } -struct NtfMessages { - var user: User - var connEntity_: ConnectionEntity? - var msgTs: Date? - var ntfMessage_: NtfMsgInfo? - - var ntfsEnabled: Bool { - user.showNotifications && (connEntity_?.ntfsEnabled ?? false) +func defaultBestAttemptNtf(_ ntfConn: NtfConn) -> NSENotificationData { + let user = ntfConn.user + let connEntity = ntfConn.connEntity + return if !user.showNotifications { + .noNtf + } else { + switch ntfConn.connEntity { + case let .rcvDirectMsgConnection(_, contact): + contact?.chatSettings.enableNtfs == .all + ? .connectionEvent(user, connEntity) + : .noNtf + case let .rcvGroupMsgConnection(_, groupInfo, _): + groupInfo.chatSettings.enableNtfs == .all + ? .connectionEvent(user, connEntity) + : .noNtf + case .sndFileConnection: .noNtf + case .rcvFileConnection: .noNtf + case let .userContactConnection(_, userContact): + userContact.groupId == nil + ? .connectionEvent(user, connEntity) + : .noNtf + } } } diff --git a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings index 5ef592ec70..ec502c53c6 100644 --- a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d neue Ereignisse"; + +/* notification body */ +"From %d chat(s)" = "Von %d Chat(s)"; + +/* notification body */ +"From: %@" = "Von: %@"; + +/* notification */ +"New events" = "Neue Ereignisse"; + +/* notification */ +"New messages" = "Neue Nachrichten"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings index 5ef592ec70..685eb3d93d 100644 --- a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d evento(s) nuevo(s)"; + +/* notification body */ +"From %d chat(s)" = "De %d chat(s)"; + +/* notification body */ +"From: %@" = "De: %@"; + +/* notification */ +"New events" = "Eventos nuevos"; + +/* notification */ +"New messages" = "Mensajes nuevos"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings b/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings index 5ef592ec70..999bb3608f 100644 --- a/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings @@ -1,7 +1,12 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d nouveaux événements"; + +/* notification body */ +"From: %@" = "De : %@"; + +/* notification */ +"New events" = "Nouveaux événements"; + +/* notification */ +"New messages" = "Nouveaux messages"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings index 5ef592ec70..a6330b93db 100644 --- a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d új esemény"; + +/* notification body */ +"From %d chat(s)" = "%d csevegésből"; + +/* notification body */ +"From: %@" = "Tőle: %@"; + +/* notification */ +"New events" = "Új események"; + +/* notification */ +"New messages" = "Új üzenetek"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings index 5ef592ec70..a6c1ec215b 100644 --- a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d nuovi eventi"; + +/* notification body */ +"From %d chat(s)" = "Da %d chat"; + +/* notification body */ +"From: %@" = "Da: %@"; + +/* notification */ +"New events" = "Nuovi eventi"; + +/* notification */ +"New messages" = "Nuovi messaggi"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings index 5ef592ec70..12d1e01f1d 100644 --- a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings @@ -1,7 +1,12 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "‐%d nieuwe gebeurtenissen"; + +/* notification body */ +"From: %@" = "Van: %@"; + +/* notification */ +"New events" = "Nieuwe gebeurtenissen"; + +/* notification */ +"New messages" = "Nieuwe berichten"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/pl.lproj/Localizable.strings b/apps/ios/SimpleX NSE/pl.lproj/Localizable.strings index 5ef592ec70..3a577620a0 100644 --- a/apps/ios/SimpleX NSE/pl.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/pl.lproj/Localizable.strings @@ -1,7 +1,3 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"New messages in %d chats" = "Nowe wiadomości w %d czatach"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings index 5ef592ec70..7205b37e7f 100644 --- a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings @@ -1,7 +1,12 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d новых сообщений"; + +/* notification body */ +"From: %@" = "От: %@"; + +/* notification */ +"New events" = "Новые события"; + +/* notification */ +"New messages" = "Новые сообщения"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings index 5ef592ec70..ceace71e34 100644 --- a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings @@ -1,7 +1,12 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d нових подій"; + +/* notification body */ +"From: %@" = "Від: %@"; + +/* notification */ +"New events" = "Нові події"; + +/* notification */ +"New messages" = "Нові повідомлення"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 47e072ae78..3e901c73eb 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -13,93 +13,92 @@ import SimpleXChat let logger = Logger() func apiGetActiveUser() throws -> User? { - let r = sendSimpleXCmd(.showActiveUser) + let r: APIResult = sendSimpleXCmd(SEChatCommand.showActiveUser) switch r { - case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): return nil - default: throw r + case let .result(.activeUser(user)): return user + case .error(.error(.noActiveUser)): return nil + default: throw r.unexpected } } func apiStartChat() throws -> Bool { - let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: true)) + let r: APIResult = sendSimpleXCmd(SEChatCommand.startChat(mainApp: false, enableSndFiles: true)) switch r { - case .chatStarted: return true - case .chatRunning: return false - default: throw r + case .result(.chatStarted): return true + case .result(.chatRunning): return false + default: throw r.unexpected } } func apiSetNetworkConfig(_ cfg: NetCfg) throws { - let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws { - let r = sendSimpleXCmd(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSetEncryptLocalFiles(enable: enable)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiGetChats(userId: User.ID) throws -> Array { - let r = sendSimpleXCmd(.apiGetChats(userId: userId)) - if case let .apiChats(user: _, chats: chats) = r { return chats } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiGetChats(userId: userId)) + if case let .result(.apiChats(user: _, chats: chats)) = r { return chats } + throw r.unexpected } -func apiSendMessage( +func apiSendMessages( chatInfo: ChatInfo, - cryptoFile: CryptoFile?, - msgContent: MsgContent -) throws -> AChatItem { - let r = sendSimpleXCmd( + composedMessages: [ComposedMessage] +) throws -> [AChatItem] { + let r: APIResult = sendSimpleXCmd( chatInfo.chatType == .local - ? .apiCreateChatItem( + ? SEChatCommand.apiCreateChatItems( noteFolderId: chatInfo.apiId, - file: cryptoFile, - msg: msgContent + composedMessages: composedMessages ) - : .apiSendMessage( + : SEChatCommand.apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, - file: cryptoFile, - quotedItemId: nil, - msg: msgContent, live: false, - ttl: nil + ttl: nil, + composedMessages: composedMessages ) ) - if case let .newChatItem(_, chatItem) = r { - return chatItem + if case let .result(.newChatItems(_, chatItems)) = r { + return chatItems } else { - if let filePath = cryptoFile?.filePath { removeFile(filePath) } - throw r + for composedMessage in composedMessages { + if let filePath = composedMessage.fileSource?.filePath { removeFile(filePath) } + } + throw r.unexpected } } func apiActivateChat() throws { chatReopenStore() - let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiActivateChat(restoreChat: false)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSuspendChat(expired: Bool) { - let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000)) + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000)) // Block until `chatSuspended` received or 3 seconds has passed var suspended = false - if case .cmdOk = r, !expired { + if case .result(.cmdOk) = r, !expired { let startTime = CFAbsoluteTimeGetCurrent() while CFAbsoluteTimeGetCurrent() - startTime < 3 { - switch recvSimpleXMsg(messageTimeout: 3_500000) { - case .chatSuspended: + let msg: APIResult? = recvSimpleXMsg(messageTimeout: 3_500000) + switch msg { + case .result(.chatSuspended): suspended = false break default: continue @@ -107,9 +106,121 @@ func apiSuspendChat(expired: Bool) { } } if !suspended { - _ = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: 0)) + let _r1: APIResult = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: 0)) } logger.debug("close store") chatCloseStore() SEChatState.shared.set(.inactive) } + +enum SEChatCommand: ChatCmdProtocol { + case showActiveUser + case startChat(mainApp: Bool, enableSndFiles: Bool) + case apiActivateChat(restoreChat: Bool) + case apiSuspendChat(timeoutMicroseconds: Int) + case apiSetNetworkConfig(networkConfig: NetCfg) + case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) + case apiSetEncryptLocalFiles(enable: Bool) + case apiGetChats(userId: Int64) + case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) + case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + + var cmdString: String { + switch self { + case .showActiveUser: return "/u" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" + case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" + case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" + case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" + case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): + return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" + case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" + case let .apiCreateChatItems(noteFolderId, composedMessages): + let msgs = encodeJSON(composedMessages) + return "/_create *\(noteFolderId) json \(msgs)" + case let .apiSendMessages(type, id, live, ttl, composedMessages): + let msgs = encodeJSON(composedMessages) + let ttlStr = ttl != nil ? "\(ttl!)" : "default" + return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + } + } + + func ref(_ type: ChatType, _ id: Int64) -> String { + "\(type.rawValue)\(id)" + } +} + +enum SEChatResponse: Decodable, ChatAPIResult { + case activeUser(user: User) + case chatStarted + case chatRunning + case apiChats(user: UserRef, chats: [ChatData]) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case cmdOk(user_: UserRef?) + + var responseType: String { + switch self { + case .activeUser: "activeUser" + case .chatStarted: "chatStarted" + case .chatRunning: "chatRunning" + case .apiChats: "apiChats" + case .newChatItems: "newChatItems" + case .cmdOk: "cmdOk" + } + } + + var details: String { + switch self { + case let .activeUser(user): return String(describing: user) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case let .apiChats(u, chats): return withUser(u, String(describing: chats)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case .cmdOk: return noDetails + } + } + + static func fallbackResult(_ type: String, _ json: NSDictionary) -> SEChatResponse? { + if type == "apiChats", let r = parseApiChats(json) { + .apiChats(user: r.user, chats: r.chats) + } else { + nil + } + } +} + +enum SEChatEvent: Decodable, ChatAPIResult { + case chatSuspended + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + + var responseType: String { + switch self { + case .chatSuspended: "chatSuspended" + case .sndFileProgressXFTP: "sndFileProgressXFTP" + case .sndFileCompleteXFTP: "sndFileCompleteXFTP" + case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" + case .sndFileError: "sndFileError" + case .sndFileWarning: "sndFileWarning" + } + } + + var details: String { + switch self { + case .chatSuspended: return noDetails + case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + } + } +} diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 5bda361126..12a775f85c 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -104,7 +104,7 @@ class ShareModel: ObservableObject { // Decode base64 images on background thread let profileImages = chats.reduce(into: Dictionary()) { dict, chatData in if let profileImage = chatData.chatInfo.image, - let uiImage = UIImage(base64Encoded: profileImage) { + let uiImage = imageFromBase64(profileImage) { dict[chatData.id] = uiImage } } @@ -141,23 +141,25 @@ class ShareModel: ObservableObject { do { SEChatState.shared.set(.sendingMessage) await waitForOtherProcessesToSuspend() - let ci = try apiSendMessage( + let chatItems = try apiSendMessages( chatInfo: selected.chatInfo, - cryptoFile: sharedContent.cryptoFile, - msgContent: sharedContent.msgContent(comment: self.comment) + composedMessages: [ComposedMessage(fileSource: sharedContent.cryptoFile, msgContent: sharedContent.msgContent(comment: self.comment))] ) if selected.chatInfo.chatType == .local { completion() } else { - await MainActor.run { self.bottomBar = .loadingBar(progress: 0) } - if let e = await handleEvents( - isGroupChat: ci.chatInfo.chatType == .group, - isWithoutFile: sharedContent.cryptoFile == nil, - chatItemId: ci.chatItem.id - ) { - await MainActor.run { errorAlert = e } - } else { - completion() + // TODO batch send: share multiple items + if let ci = chatItems.first { + await MainActor.run { self.bottomBar = .loadingBar(progress: 0) } + if let e = await handleEvents( + isGroupChat: ci.chatInfo.chatType == .group, + isWithoutFile: sharedContent.cryptoFile == nil, + chatItemId: ci.chatItem.id + ) { + await MainActor.run { errorAlert = e } + } else { + completion() + } } } } catch { @@ -177,7 +179,7 @@ class ShareModel: ObservableObject { resetChatCtrl() // Clears retained migration result registerGroupDefaults() haskell_init_se() - let (_, result) = chatMigrateInit(dbKey, confirmMigrations: defaultMigrationConfirmation()) + let (_, result) = chatMigrateInit(dbKey, confirmMigrations: defaultMigrationConfirmation(), backgroundMode: false) if let e = migrationError(result) { return e } try apiSetAppFilePaths( filesFolder: getAppFilesDirectory().path, @@ -301,8 +303,9 @@ class ShareModel: ObservableObject { } } } - switch recvSimpleXMsg(messageTimeout: 1_000_000) { - case let .sndFileProgressXFTP(_, ci, _, sentSize, totalSize): + let r: APIResult? = recvSimpleXMsg(messageTimeout: 1_000_000) + switch r { + case let .result(.sndFileProgressXFTP(_, ci, _, sentSize, totalSize)): guard isMessage(for: ci) else { continue } networkTimeout = CFAbsoluteTimeGetCurrent() await MainActor.run { @@ -311,14 +314,15 @@ class ShareModel: ObservableObject { bottomBar = .loadingBar(progress: progress) } } - case let .sndFileCompleteXFTP(_, ci, _): + case let .result(.sndFileCompleteXFTP(_, ci, _)): guard isMessage(for: ci) else { continue } if isGroupChat { await MainActor.run { bottomBar = .loadingSpinner } } await ch.completeFile() if await !ch.isRunning { break } - case let .chatItemStatusUpdated(_, ci): + case let .result(.chatItemsStatusesUpdated(_, chatItems)): + guard let ci = chatItems.last else { continue } guard isMessage(for: ci) else { continue } if let (title, message) = ci.chatItem.meta.itemStatus.statusInfo { // `title` and `message` already localized and interpolated @@ -339,17 +343,15 @@ class ShareModel: ObservableObject { } } } - case let .sndFileError(_, ci, _, errorMessage): + case let .result(.sndFileError(_, ci, _, errorMessage)): guard isMessage(for: ci) else { continue } if let ci { cleanupFile(ci) } return ErrorAlert(title: "File error", message: "\(fileErrorInfo(ci) ?? errorMessage)") - case let .sndFileWarning(_, ci, _, errorMessage): + case let .result(.sndFileWarning(_, ci, _, errorMessage)): guard isMessage(for: ci) else { continue } if let ci { cleanupFile(ci) } return ErrorAlert(title: "File error", message: "\(fileErrorInfo(ci) ?? errorMessage)") - case let .chatError(_, chatError): - return ErrorAlert(chatError) - case let .chatCmdError(_, chatError): + case let .error(chatError): return ErrorAlert(chatError) default: continue } @@ -414,7 +416,7 @@ fileprivate func getSharedContent(_ ip: NSItemProvider) async -> Result Result Result some View { - if let img = UIImage(base64Encoded: img) { + @ViewBuilder private func imagePreview(_ imgStr: String) -> some View { + if let img = imageFromBase64(imgStr) { previewArea { Image(uiImage: img) .resizable() @@ -160,10 +160,10 @@ struct ShareView: View { } } - @ViewBuilder private func linkPreview(_ linkPreview: LinkPreview) -> some View { + private func linkPreview(_ linkPreview: LinkPreview) -> some View { previewArea { HStack(alignment: .center, spacing: 8) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/SimpleX SE/de.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/de.lproj/InfoPlist.strings index 48f774742e..4a387a4361 100644 --- a/apps/ios/SimpleX SE/de.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/de.lproj/InfoPlist.strings @@ -5,5 +5,5 @@ "CFBundleName" = "SimpleX SE"; /* Copyright (human-readable) */ -"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. Alle Rechte vorbehalten."; +"NSHumanReadableCopyright" = "Copyright © 2025 SimpleX Chat. Alle Rechte vorbehalten."; diff --git a/apps/ios/SimpleX SE/de.lproj/Localizable.strings b/apps/ios/SimpleX SE/de.lproj/Localizable.strings index 081d7f8c66..4c10694986 100644 --- a/apps/ios/SimpleX SE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/de.lproj/Localizable.strings @@ -17,13 +17,13 @@ "Comment" = "Kommentieren"; /* No comment provided by engineer. */ -"Currently maximum supported file size is %@." = "Die maximale erlaubte Dateigröße beträgt aktuell %@."; +"Currently maximum supported file size is %@." = "Die maximal erlaubte Dateigröße beträgt aktuell %@."; /* No comment provided by engineer. */ -"Database downgrade required" = "Datenbank-Herabstufung erforderlich"; +"Database downgrade required" = "Datenbank-Herunterstufung ist erforderlich"; /* No comment provided by engineer. */ -"Database encrypted!" = "Datenbank verschlüsselt!"; +"Database encrypted!" = "Datenbank ist verschlüsselt!"; /* No comment provided by engineer. */ "Database error" = "Datenbankfehler"; @@ -32,7 +32,7 @@ "Database passphrase is different from saved in the keychain." = "Das Datenbank-Passwort unterscheidet sich vom im Schlüsselbund gespeicherten."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Ein Datenbank-Passwort ist erforderlich, um den Chat zu öffnen."; +"Database passphrase is required to open chat." = "Um den Chat zu öffnen, ist ein Datenbank-Passwort ist erforderlich."; /* No comment provided by engineer. */ "Database upgrade required" = "Datenbank-Aktualisierung erforderlich"; @@ -47,7 +47,7 @@ "Error: %@" = "Fehler: %@"; /* No comment provided by engineer. */ -"File error" = "Dateifehler"; +"File error" = "Datei-Fehler"; /* No comment provided by engineer. */ "Incompatible database version" = "Datenbank-Version nicht kompatibel"; @@ -68,16 +68,16 @@ "Ok" = "OK"; /* No comment provided by engineer. */ -"Open the app to downgrade the database." = "Öffne die App, um die Datenbank herabzustufen."; +"Open the app to downgrade the database." = "Öffnen Sie die App, um die Datenbank herunterzustufen."; /* No comment provided by engineer. */ -"Open the app to upgrade the database." = "Öffne die App, um die Datenbank zu aktualisieren."; +"Open the app to upgrade the database." = "Öffnen Sie die App, um die Datenbank zu aktualisieren."; /* No comment provided by engineer. */ "Passphrase" = "Passwort"; /* No comment provided by engineer. */ -"Please create a profile in the SimpleX app" = "Bitte erstelle ein Profil in der SimpleX-App"; +"Please create a profile in the SimpleX app" = "Bitte erstellen Sie ein Profil in der SimpleX-App"; /* No comment provided by engineer. */ "Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt."; @@ -107,5 +107,5 @@ "Wrong database passphrase" = "Falsches Datenbank-Passwort"; /* No comment provided by engineer. */ -"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Du kannst das Teilen in den Einstellungen zu Datenschutz & Sicherheit - SimpleX-Sperre erlauben."; +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Sie können das Teilen in den Einstellungen zu Datenschutz & Sicherheit / SimpleX-Sperre erlauben."; diff --git a/apps/ios/SimpleX SE/es.lproj/Localizable.strings b/apps/ios/SimpleX SE/es.lproj/Localizable.strings index 5ecdd410df..4cc5029537 100644 --- a/apps/ios/SimpleX SE/es.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/es.lproj/Localizable.strings @@ -29,7 +29,7 @@ "Database error" = "Error en base de datos"; /* No comment provided by engineer. */ -"Database passphrase is different from saved in the keychain." = "La contraseña de la base de datos es distinta a la almacenada en keychain."; +"Database passphrase is different from saved in the keychain." = "La contraseña de la base de datos es diferente a la almacenada en keychain."; /* No comment provided by engineer. */ "Database passphrase is required to open chat." = "Se requiere la contraseña de la base de datos para abrir la aplicación."; diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings index 8ce4317a9b..2fedf0e6f1 100644 --- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings @@ -2,7 +2,7 @@ "%@" = "%@"; /* No comment provided by engineer. */ -"App is locked!" = "Az alkalmazás zárolva!"; +"App is locked!" = "Az alkalmazás zárolva van!"; /* No comment provided by engineer. */ "Cancel" = "Mégse"; @@ -17,7 +17,7 @@ "Comment" = "Hozzászólás"; /* No comment provided by engineer. */ -"Currently maximum supported file size is %@." = "Jelenleg a maximális támogatott fájlméret %@."; +"Currently maximum supported file size is %@." = "Jelenleg támogatott legnagyobb fájl méret: %@."; /* No comment provided by engineer. */ "Database downgrade required" = "Adatbázis visszafejlesztése szükséges"; @@ -26,22 +26,22 @@ "Database encrypted!" = "Adatbázis titkosítva!"; /* No comment provided by engineer. */ -"Database error" = "Adatbázis hiba"; +"Database error" = "Adatbázishiba"; /* No comment provided by engineer. */ -"Database passphrase is different from saved in the keychain." = "Az adatbázis jelmondata eltér a kulcstartóban lévőtől."; +"Database passphrase is different from saved in the keychain." = "Az adatbázis jelmondata nem egyezik a kulcstartóba mentettől."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Adatbázis jelmondat szükséges a csevegés megnyitásához."; +"Database passphrase is required to open chat." = "A csevegés megnyitásához adja meg az adatbázis jelmondatát."; /* No comment provided by engineer. */ "Database upgrade required" = "Adatbázis fejlesztése szükséges"; /* No comment provided by engineer. */ -"Error preparing file" = "Hiba a fájl előkészítésekor"; +"Error preparing file" = "Hiba történt a fájl előkészítésekor"; /* No comment provided by engineer. */ -"Error preparing message" = "Hiba az üzenet előkészítésekor"; +"Error preparing message" = "Hiba történt az üzenet előkészítésekor"; /* No comment provided by engineer. */ "Error: %@" = "Hiba: %@"; @@ -50,13 +50,13 @@ "File error" = "Fájlhiba"; /* No comment provided by engineer. */ -"Incompatible database version" = "Nem kompatibilis adatbázis verzió"; +"Incompatible database version" = "Nem kompatibilis adatbázis-verzió"; /* No comment provided by engineer. */ "Invalid migration confirmation" = "Érvénytelen átköltöztetési visszaigazolás"; /* No comment provided by engineer. */ -"Keychain error" = "Kulcstartó hiba"; +"Keychain error" = "Kulcstartóhiba"; /* No comment provided by engineer. */ "Large file!" = "Nagy fájl!"; @@ -80,7 +80,7 @@ "Please create a profile in the SimpleX app" = "Hozzon létre egy profilt a SimpleX alkalmazásban"; /* No comment provided by engineer. */ -"Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet."; +"Selected chat preferences prohibit this message." = "A kijelölt csevegési beállítások tiltják ezt az üzenetet."; /* No comment provided by engineer. */ "Sending a message takes longer than expected." = "Az üzenet elküldése a vártnál tovább tart."; @@ -92,10 +92,10 @@ "Share" = "Megosztás"; /* No comment provided by engineer. */ -"Slow network?" = "Lassú internetkapcsolat?"; +"Slow network?" = "Lassú a hálózata?"; /* No comment provided by engineer. */ -"Unknown database error: %@" = "Ismeretlen adatbázis hiba: %@"; +"Unknown database error: %@" = "Ismeretlen adatbázishiba: %@"; /* No comment provided by engineer. */ "Unsupported format" = "Nem támogatott formátum"; @@ -104,8 +104,8 @@ "Wait" = "Várjon"; /* No comment provided by engineer. */ -"Wrong database passphrase" = "Hibás adatbázis jelmondat"; +"Wrong database passphrase" = "Érvénytelen adatbázis-jelmondat"; /* No comment provided by engineer. */ -"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX zár menüben engedélyezheti."; +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX-zár menüben engedélyezheti."; diff --git a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings index 8412c88ea6..e5d2487b54 100644 --- a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings @@ -32,7 +32,7 @@ "Database passphrase is different from saved in the keychain." = "Het wachtwoord van de database verschilt van het wachtwoord die in de keychain is opgeslagen."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je gesprekken te openen."; +"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je chats te openen."; /* No comment provided by engineer. */ "Database upgrade required" = "Database upgrade vereist"; diff --git a/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings index 388ac01f7f..cf1ca31f53 100644 --- a/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "Telif Hakkı © 2024 SimpleX Chat. Tüm hakları saklıdır."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/tr.lproj/Localizable.strings b/apps/ios/SimpleX SE/tr.lproj/Localizable.strings index 5ef592ec70..baef71c127 100644 --- a/apps/ios/SimpleX SE/tr.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/tr.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "Uygulama kilitlendi!"; + +/* No comment provided by engineer. */ +"Cancel" = "İptal et"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor"; + +/* No comment provided by engineer. */ +"Cannot forward message" = "Mesaj iletilemiyor"; + +/* No comment provided by engineer. */ +"Comment" = "Yorum"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "Şu anki maksimum desteklenen dosya boyutu %@ kadardır."; + +/* No comment provided by engineer. */ +"Database downgrade required" = "Veritabanı sürüm düşürme gerekli"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "Veritabanı şifrelendi!"; + +/* No comment provided by engineer. */ +"Database error" = "Veritabanı hatası"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "Veritabanı parolası Anahtar Zinciri'nde kayıtlı olandan farklıdır."; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "Konuşmayı açmak için veri tabanı parolası gerekli."; + +/* No comment provided by engineer. */ +"Database upgrade required" = "Veritabanı yükseltmesi gerekli"; + +/* No comment provided by engineer. */ +"Error preparing file" = "Dosya hazırlanırken hata oluştu"; + +/* No comment provided by engineer. */ +"Error preparing message" = "Mesaj hazırlanırken hata oluştu"; + +/* No comment provided by engineer. */ +"Error: %@" = "Hata: %@"; + +/* No comment provided by engineer. */ +"File error" = "Dosya hatası"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "Uyumsuz veritabanı sürümü"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Geçerli olmayan taşıma onayı"; + +/* No comment provided by engineer. */ +"Keychain error" = "Anahtarlık hatası"; + +/* No comment provided by engineer. */ +"Large file!" = "Büyük dosya!"; + +/* No comment provided by engineer. */ +"No active profile" = "Aktif profil yok"; + +/* No comment provided by engineer. */ +"Ok" = "Tamam"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "Veritabanının sürümünü düşürmek için uygulamayı açın."; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "Veritabanını güncellemek için uygulamayı açın."; + +/* No comment provided by engineer. */ +"Passphrase" = "Parola"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "Lütfen SimpleX uygulamasında bir profil oluşturun"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Seçilen sohbet tercihleri bu mesajı yasakladı."; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "Mesaj göndermek beklenenden daha uzun sürüyor."; + +/* No comment provided by engineer. */ +"Sending message…" = "Mesaj gönderiliyor…"; + +/* No comment provided by engineer. */ +"Share" = "Paylaş"; + +/* No comment provided by engineer. */ +"Slow network?" = "Ağ yavaş mı?"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "Bilinmeyen veritabanı hatası: %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "Desteklenmeyen format"; + +/* No comment provided by engineer. */ +"Wait" = "Bekleyin"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "Yanlış veritabanı parolası"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Gizlilik ve Güvenlik / SimpleX Lock ayarlarından paylaşıma izin verebilirsiniz."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings index 388ac01f7f..760be62885 100644 --- a/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "版权所有 © 2024 SimpleX Chat。保留所有权利。"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings index 5ef592ec70..362e2edb74 100644 --- a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "应用程序已锁定!"; + +/* No comment provided by engineer. */ +"Cancel" = "取消"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "无法访问钥匙串以保存数据库密码"; + +/* No comment provided by engineer. */ +"Cannot forward message" = "无法转发消息"; + +/* No comment provided by engineer. */ +"Comment" = "评论"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "当前支持的最大文件大小为 %@。"; + +/* No comment provided by engineer. */ +"Database downgrade required" = "需要数据库降级"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "数据库已加密!"; + +/* No comment provided by engineer. */ +"Database error" = "数据库错误"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "数据库密码与保存在钥匙串中的密码不同。"; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "需要数据库密码才能打开聊天。"; + +/* No comment provided by engineer. */ +"Database upgrade required" = "需要升级数据库"; + +/* No comment provided by engineer. */ +"Error preparing file" = "准备文件时出错"; + +/* No comment provided by engineer. */ +"Error preparing message" = "准备消息时出错"; + +/* No comment provided by engineer. */ +"Error: %@" = "错误:%@"; + +/* No comment provided by engineer. */ +"File error" = "文件错误"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "不兼容的数据库版本"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "无效的迁移确认"; + +/* No comment provided by engineer. */ +"Keychain error" = "钥匙串错误"; + +/* No comment provided by engineer. */ +"Large file!" = "大文件!"; + +/* No comment provided by engineer. */ +"No active profile" = "无活动配置文件"; + +/* No comment provided by engineer. */ +"Ok" = "好的"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "打开应用程序以降级数据库。"; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "打开应用程序以升级数据库。"; + +/* No comment provided by engineer. */ +"Passphrase" = "密码"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "请在 SimpleX 应用程序中创建配置文件"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "选定的聊天首选项禁止此消息。"; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "发送消息所需的时间比预期的要长。"; + +/* No comment provided by engineer. */ +"Sending message…" = "正在发送消息…"; + +/* No comment provided by engineer. */ +"Share" = "共享"; + +/* No comment provided by engineer. */ +"Slow network?" = "网络速度慢?"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "未知数据库错误: %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "不支持的格式"; + +/* No comment provided by engineer. */ +"Wait" = "等待"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "数据库密码错误"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "您可以在 \"隐私与安全\"/\"SimpleX Lock \"设置中允许共享。"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 48689d1010..9326ae9abe 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -139,20 +139,21 @@ 5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; }; 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; }; 5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; }; - 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; }; 5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; }; 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; }; + 640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640743602CD360E600158442 /* ChooseServerOperators.swift */; }; 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; }; 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; + 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; + 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; }; 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; }; - 64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; }; 64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; }; 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; }; 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; }; @@ -171,12 +172,19 @@ 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; 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.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.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 */; }; 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; }; 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; }; 64EEB0F72C353F1C00972D62 /* ServersSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */; }; 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; }; + 8C01E9C12C8EFC33008A4B0A /* objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C01E9C02C8EFC33008A4B0A /* objc.m */; }; + 8C01E9C22C8EFF8F008A4B0A /* objc.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C01E9BF2C8EFBB6008A4B0A /* objc.h */; }; 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; }; 8C74C3E52C1B900600039E77 /* ThemeTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7E3CE32C0DEAC400BFF63A /* ThemeTypes.swift */; }; 8C74C3E72C1B901900039E77 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C852B072C1086D100BA61E8 /* Color.swift */; }; @@ -190,10 +198,24 @@ 8C8118722C220B5B00E6FC94 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 8C8118712C220B5B00E6FC94 /* Yams */; }; 8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; }; 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; }; + 8CAD466F2D15A8100078D18F /* ChatScrollHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAD466E2D15A8100078D18F /* ChatScrollHelpers.swift */; }; + 8CAEF1502D11A6A000240F00 /* ChatItemsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAEF14F2D11A6A000240F00 /* ChatItemsLoader.swift */; }; + 8CB15EA02CFDA30600C28209 /* ChatItemsMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB15E9F2CFDA30600C28209 /* ChatItemsMerger.swift */; }; + 8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 8CB3476B2CF5CFFA006787A5 /* Ink */; }; + 8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */; }; + 8CBC14862D357CDB00BBD901 /* StorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CBC14852D357CDB00BBD901 /* StorageView.swift */; }; + 8CC317442D4FEB9B00292A20 /* EndlessScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC317432D4FEB9B00292A20 /* EndlessScrollView.swift */; }; + 8CC317462D4FEBA800292A20 /* ScrollViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC317452D4FEBA800292A20 /* ScrollViewCells.swift */; }; 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; }; 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; 8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; }; + B70A39732D24090D00E80A5F /* TagListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B70A39722D24090D00E80A5F /* TagListView.swift */; }; + B70CE9E62D4BE5930080F36D /* GroupMentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B70CE9E52D4BE5930080F36D /* GroupMentions.swift */; }; + B728945B2D0C62BF00F7A19A /* ElegantEmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = B728945A2D0C62BF00F7A19A /* ElegantEmojiPicker */; }; + B73EFE532CE5FA3500C778EA /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73EFE522CE5FA3500C778EA /* CreateSimpleXAddress.swift */; }; B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */; }; + B79ADAFF2CE4EF930083DFFD /* AddressCreationCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79ADAFE2CE4EF930083DFFD /* AddressCreationCard.swift */; }; + CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */; }; CE1EB0E42C459A660099D896 /* ShareAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1EB0E32C459A660099D896 /* ShareAPI.swift */; }; CE2AD9CE2C452A4D00E844E3 /* ChatUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */; }; CE3097FB2C4C0C9F00180898 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */; }; @@ -201,12 +223,14 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; + CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */; }; + CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; CEE723B12C3BD3D70009AE93 /* SimpleX SE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CEE723F02C3D25C70009AE93 /* ShareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723EF2C3D25C70009AE93 /* ShareView.swift */; }; CEE723F22C3D25ED0009AE93 /* ShareModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723F12C3D25ED0009AE93 /* ShareModel.swift */; }; - CEEA861D2C2ABCB50084E1EA /* ReverseList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEA861C2C2ABCB50084E1EA /* ReverseList.swift */; }; + CEFB2EDF2CA1BCC7004B1ECE /* SheetRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; }; D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547729AF89AF0022400A /* StoreKit.framework */; }; @@ -214,15 +238,12 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5852C7A26FE009F2C7C /* libffi.a */; }; - E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5862C7A26FE009F2C7C /* libgmpxx.a */; }; - E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5872C7A26FE009F2C7C /* libgmp.a */; }; - E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */; }; - E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; + E5DDBE6E2DC4106800A0EFF0 /* AppAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */; }; + E5DDBE702DC4217900A0EFF0 /* NSEAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -430,7 +451,7 @@ 5CB634AC29E46CF70066AD6B /* LocalAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthView.swift; sourceTree = ""; }; 5CB634AE29E4BB7D0066AD6B /* SetAppPasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetAppPasscodeView.swift; sourceTree = ""; }; 5CB634B029E5EFEA0066AD6B /* PasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; - 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; wrapsLines = 0; }; 5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListNavLink.swift; sourceTree = ""; }; 5CBD285529565CAE00EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -477,19 +498,20 @@ 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = ""; }; 5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = ""; }; 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = ""; }; - 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = ""; }; 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; }; 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = ""; }; 640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = ""; }; + 640743602CD360E600158442 /* ChooseServerOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseServerOperators.swift; sourceTree = ""; }; 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = ""; }; 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; + 642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; + 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = ""; }; 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = ""; }; - 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = ""; }; 64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = ""; }; 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = ""; }; 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = ""; }; @@ -509,6 +531,11 @@ 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; 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.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.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 = ""; }; 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactLearnMore.swift; sourceTree = ""; }; @@ -516,6 +543,8 @@ 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = ""; }; 64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersSummaryView.swift; sourceTree = ""; }; 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = ""; }; + 8C01E9BF2C8EFBB6008A4B0A /* objc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = objc.h; sourceTree = ""; }; + 8C01E9C02C8EFC33008A4B0A /* objc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = objc.m; sourceTree = ""; }; 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; 8C74C3EB2C1B92A900039E77 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 8C74C3ED2C1B942300039E77 /* ChatWallpaper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatWallpaper.swift; sourceTree = ""; }; @@ -528,15 +557,29 @@ 8C852B072C1086D100BA61E8 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 8C86EBE42C0DAE4F00E12243 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeModeEditor.swift; sourceTree = ""; }; + 8CAD466E2D15A8100078D18F /* ChatScrollHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScrollHelpers.swift; sourceTree = ""; }; + 8CAEF14F2D11A6A000240F00 /* ChatItemsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemsLoader.swift; sourceTree = ""; }; + 8CB15E9F2CFDA30600C28209 /* ChatItemsMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemsMerger.swift; sourceTree = ""; }; + 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsWebView.swift; sourceTree = ""; }; + 8CBC14852D357CDB00BBD901 /* StorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageView.swift; sourceTree = ""; }; + 8CC317432D4FEB9B00292A20 /* EndlessScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EndlessScrollView.swift; sourceTree = ""; }; + 8CC317452D4FEBA800292A20 /* ScrollViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewCells.swift; sourceTree = ""; }; 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = ""; }; 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = ""; }; + B70A39722D24090D00E80A5F /* TagListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagListView.swift; sourceTree = ""; }; + B70CE9E52D4BE5930080F36D /* GroupMentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMentions.swift; sourceTree = ""; }; + B73EFE522CE5FA3500C778EA /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = ""; }; B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListNavLink.swift; sourceTree = ""; }; + B79ADAFE2CE4EF930083DFFD /* AddressCreationCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCreationCard.swift; sourceTree = ""; }; + CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvertedForegroundStyle.swift; sourceTree = ""; }; CE1EB0E32C459A660099D896 /* ShareAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAPI.swift; sourceTree = ""; }; CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUtils.swift; sourceTree = ""; }; CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = ""; }; CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; + CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = ""; }; + CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = ""; }; CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = ""; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; @@ -544,17 +587,12 @@ CEE723D42C3C21F50009AE93 /* SimpleX SE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX SE.entitlements"; sourceTree = ""; }; CEE723EF2C3D25C70009AE93 /* ShareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareView.swift; sourceTree = ""; }; CEE723F12C3D25ED0009AE93 /* ShareModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareModel.swift; sourceTree = ""; }; - CEEA861C2C2ABCB50084E1EA /* ReverseList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseList.swift; sourceTree = ""; }; + CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetRepresentable.swift; sourceTree = ""; }; D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = ""; }; D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; 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 = ""; }; - E51ED5852C7A26FE009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E51ED5862C7A26FE009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5872C7A26FE009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a"; sourceTree = ""; }; - E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -607,6 +645,8 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; + E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAPITypes.swift; sourceTree = ""; }; + E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEAPITypes.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -615,7 +655,9 @@ buildActionMask = 2147483647; files = ( 5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */, + B728945B2D0C62BF00F7A19A /* ElegantEmojiPicker in Frameworks */, 8C8118722C220B5B00E6FC94 /* Yams in Frameworks */, + 8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */, D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */, D7197A1829AE89660055C05A /* WebRTC in Frameworks */, D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */, @@ -645,14 +687,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED58D2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a in Frameworks */, - E51ED58E2C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED58B2C7A26FE009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, + 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, + 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED58C2C7A26FE009F2C7C /* libgmp.a in Frameworks */, - E51ED58A2C7A26FE009F2C7C /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -707,6 +749,8 @@ 5C5F4AC227A5E9AF00B51EF1 /* Chat */ = { isa = PBXGroup; children = ( + 8CC317452D4FEBA800292A20 /* ScrollViewCells.swift */, + 8CC317432D4FEB9B00292A20 /* EndlessScrollView.swift */, 6440CA01288AEC770062C672 /* Group */, 5CE4407427ADB657007B033A /* ChatItem */, 5CEACCE527DE977C000BD591 /* ComposeMessage */, @@ -717,11 +761,13 @@ 5CE4407127ADB1D0007B033A /* Emoji.swift */, 5CADE79B292131E900072E13 /* ContactPreferencesView.swift */, 5CBE6C11294487F7002D9531 /* VerifyCodeView.swift */, - CEEA861C2C2ABCB50084E1EA /* ReverseList.swift */, 5CBE6C132944CC12002D9531 /* ScanCodeView.swift */, 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */, 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */, 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */, + 8CB15E9F2CFDA30600C28209 /* ChatItemsMerger.swift */, + 8CAEF14F2D11A6A000240F00 /* ChatItemsLoader.swift */, + 8CAD466E2D15A8100078D18F /* ChatScrollHelpers.swift */, ); path = Chat; sourceTree = ""; @@ -729,11 +775,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5852C7A26FE009F2C7C /* libffi.a */, - E51ED5872C7A26FE009F2C7C /* libgmp.a */, - E51ED5862C7A26FE009F2C7C /* libgmpxx.a */, - E51ED5892C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4-ghc9.6.3.a */, - E51ED5882C7A26FE009F2C7C /* libHSsimplex-chat-6.0.3.0-JVz5IxfwvrHaD2mJGTgT4.a */, + 64C829992D54AEEE006B9E89 /* libffi.a */, + 64C829982D54AEED006B9E89 /* libgmp.a */, + 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */, ); path = Libraries; sourceTree = ""; @@ -753,6 +799,7 @@ 5C764E87279CBC8E000C6508 /* Model */ = { isa = PBXGroup; children = ( + E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */, 5C764E88279CBCB3000C6508 /* ChatModel.swift */, 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */, 5C35CFC727B2782E00FB6C6D /* BGManager.swift */, @@ -789,6 +836,10 @@ 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, CE7548092C622630009579B7 /* SwipeLabel.swift */, + CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, + CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */, + CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */, + CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */, ); path = Helpers; sourceTree = ""; @@ -859,13 +910,15 @@ 5CB0BA8C282711BC00B3292C /* Onboarding */ = { isa = PBXGroup; children = ( + B73EFE522CE5FA3500C778EA /* CreateSimpleXAddress.swift */, 5CB0BA8D2827126500B3292C /* OnboardingView.swift */, 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */, 5CB0BA992827FD8800B3292C /* HowItWorks.swift */, 5CB0BA91282713FD00B3292C /* CreateProfile.swift */, - 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */, 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */, 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */, + 640743602CD360E600158442 /* ChooseServerOperators.swift */, + B79ADAFE2CE4EF930083DFFD /* AddressCreationCard.swift */, ); path = Onboarding; sourceTree = ""; @@ -896,10 +949,9 @@ 5CB924DF27A8678B00ACCCDD /* UserSettings */ = { isa = PBXGroup; children = ( + 643B3B4C2CCFD34B0083A2CF /* NetworkAndServers */, 5CB924D627A8563F00ACCCDD /* SettingsView.swift */, 5CB346E62868D76D001FD2EF /* NotificationsView.swift */, - 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */, - 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */, 5CADE79929211BB900072E13 /* PreferencesView.swift */, 5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */, 5C05DF522840AA1D00C683F9 /* CallSettings.swift */, @@ -907,9 +959,6 @@ 5CC036DF29C488D500C0EF20 /* HiddenProfileView.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */, - 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */, - 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */, - 5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */, 5CB2084E28DA4B4800D024EC /* RTCServers.swift */, 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */, 18415845648CA4F5A8BCA272 /* UserProfilesView.swift */, @@ -919,6 +968,7 @@ 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */, 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */, 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */, + 8CBC14852D357CDB00BBD901 /* StorageView.swift */, ); path = UserSettings; sourceTree = ""; @@ -936,6 +986,7 @@ 18415835CBD939A9ABDC108A /* UserPicker.swift */, 64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */, E51CC1E52C62085600DB91FE /* OneHandUICard.swift */, + B70A39722D24090D00E80A5F /* TagListView.swift */, ); path = ChatList; sourceTree = ""; @@ -944,6 +995,7 @@ isa = PBXGroup; children = ( 5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */, + E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */, 5CDCAD472818589900503DA2 /* NotificationService.swift */, 5CDCAD492818589900503DA2 /* Info.plist */, 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */, @@ -960,9 +1012,9 @@ 5CDCAD7228188CFF00503DA2 /* ChatTypes.swift */, 5CDCAD7428188D2900503DA2 /* APITypes.swift */, 5C5E5D3C282447AB00B0488A /* CallTypes.swift */, + 5CDCAD7D2818941F00503DA2 /* API.swift */, CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */, 5C9FD96A27A56D4D0075386C /* JSON.swift */, - 5CDCAD7D2818941F00503DA2 /* API.swift */, 5CDCAD80281A7E2700503DA2 /* Notifications.swift */, 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */, CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */, @@ -976,6 +1028,8 @@ 5CE2BA96284537A800EC33A6 /* dummy.m */, 5CD67B8D2B0E858A00C510B1 /* hs_init.h */, 5CD67B8E2B0E858A00C510B1 /* hs_init.c */, + 8C01E9BF2C8EFBB6008A4B0A /* objc.h */, + 8C01E9C02C8EFC33008A4B0A /* objc.m */, ); path = SimpleXChat; sourceTree = ""; @@ -1030,7 +1084,6 @@ isa = PBXGroup; children = ( 5C4B3B09285FB130003915F2 /* DatabaseView.swift */, - 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */, 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */, 5C9CC7A828C532AB00BEF955 /* DatabaseErrorView.swift */, 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */, @@ -1038,6 +1091,21 @@ path = Database; sourceTree = ""; }; + 643B3B4C2CCFD34B0083A2CF /* NetworkAndServers */ = { + isa = PBXGroup; + children = ( + 5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */, + 642BA82C2CE50495005E9412 /* NewServerView.swift */, + 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */, + 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */, + 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */, + 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */, + 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */, + 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */, + ); + path = NetworkAndServers; + sourceTree = ""; + }; 6440CA01288AEC770062C672 /* Group */ = { isa = PBXGroup; children = ( @@ -1048,6 +1116,7 @@ 5C9C2DA42894777E00CC63B1 /* GroupProfileView.swift */, 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */, 1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */, + B70CE9E52D4BE5930080F36D /* GroupMentions.swift */, ); path = Group; sourceTree = ""; @@ -1113,6 +1182,7 @@ files = ( 5CE2BA77284530BF00EC33A6 /* SimpleXChat.h in Headers */, 5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */, + 8C01E9C22C8EFF8F008A4B0A /* objc.h in Headers */, 5CE2BA952845354B00EC33A6 /* SimpleX.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1144,6 +1214,8 @@ D7F0E33829964E7E0068AF69 /* LZString */, D7197A1729AE89660055C05A /* WebRTC */, 8C8118712C220B5B00E6FC94 /* Yams */, + 8CB3476B2CF5CFFA006787A5 /* Ink */, + B728945A2D0C62BF00F7A19A /* ElegantEmojiPicker */, ); productName = "SimpleX (iOS)"; productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */; @@ -1287,6 +1359,8 @@ D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */, D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */, 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */, + 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */, + B72894592D0C62BF00F7A19A /* XCRemoteSwiftPackageReference "Elegant-Emoji-Picker" */, ); productRefGroup = 5CA059CB279559F40002BEB4 /* Products */; projectDirPath = ""; @@ -1352,14 +1426,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CEEA861D2C2ABCB50084E1EA /* ReverseList.swift in Sources */, 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */, + 8CC317442D4FEB9B00292A20 /* EndlessScrollView.swift in Sources */, 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */, 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */, + 640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */, 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */, 5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */, 5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */, 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */, + B79ADAFF2CE4EF930083DFFD /* AddressCreationCard.swift in Sources */, 5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */, E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */, 5C65DAF929D0CC20003CEE45 /* DeveloperView.swift in Sources */, @@ -1369,6 +1445,7 @@ 5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */, 644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */, 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */, + 8CC317462D4FEBA800292A20 /* ScrollViewCells.swift in Sources */, 5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */, 5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */, 8C74C3EC2C1B92A900039E77 /* Theme.swift in Sources */, @@ -1386,17 +1463,18 @@ 644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */, 5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */, 5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */, + B73EFE532CE5FA3500C778EA /* CreateSimpleXAddress.swift in Sources */, 5CB634AF29E4BB7D0066AD6B /* SetAppPasscodeView.swift in Sources */, 5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */, 5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */, 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */, 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */, - 64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */, 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */, 5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */, B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */, 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */, D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */, + B70CE9E62D4BE5930080F36D /* GroupMentions.swift in Sources */, CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */, 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */, 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, @@ -1408,6 +1486,7 @@ 8C7F8F0E2C19C0C100D16888 /* ViewModifiers.swift in Sources */, 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */, 5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */, + 8CBC14862D357CDB00BBD901 /* StorageView.swift in Sources */, 5C9A5BDB2871E05400A5B906 /* SetNotificationsMode.swift in Sources */, 5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */, 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */, @@ -1436,6 +1515,7 @@ 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */, 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */, 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, + CEFB2EDF2CA1BCC7004B1ECE /* SheetRepresentable.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */, 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */, @@ -1455,17 +1535,20 @@ 5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */, 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */, 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */, + 8CAD466F2D15A8100078D18F /* ChatScrollHelpers.swift in Sources */, 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */, 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */, + E5DDBE6E2DC4106800A0EFF0 /* AppAPITypes.swift in Sources */, 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */, 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */, 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */, - 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */, + CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */, 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */, + 8CAEF1502D11A6A000240F00 /* ChatItemsLoader.swift in Sources */, 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */, 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, 8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */, @@ -1473,8 +1556,10 @@ 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */, 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */, + 8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */, 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */, + B70A39732D24090D00E80A5F /* TagListView.swift in Sources */, 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */, 6440CA00288857A10062C672 /* CIEventView.swift in Sources */, 5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */, @@ -1486,9 +1571,11 @@ 5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */, 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */, 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */, + CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */, 5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */, 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */, + CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */, 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */, 5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */, 5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */, @@ -1501,11 +1588,14 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */, 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */, 18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */, + 8CB15EA02CFDA30600C28209 /* ChatItemsMerger.swift in Sources */, 184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */, 5CB634AD29E46CF70066AD6B /* LocalAuthView.swift in Sources */, 18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */, 18415F9A2D551F9757DA4654 /* CIVideoView.swift in Sources */, + 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */, 184158C131FDB829D8A117EA /* VideoPlayerView.swift in Sources */, + 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1523,6 +1613,7 @@ buildActionMask = 2147483647; files = ( 5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */, + E5DDBE702DC4217900A0EFF0 /* NSEAPITypes.swift in Sources */, 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1539,6 +1630,7 @@ 8C74C3E72C1B901900039E77 /* Color.swift in Sources */, 5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */, 5CE2BA91284533A300EC33A6 /* Notifications.swift in Sources */, + 8C01E9C12C8EFC33008A4B0A /* objc.m in Sources */, 5CE2BA79284530CC00EC33A6 /* SimpleXChat.docc in Sources */, 5CE2BA90284533A300EC33A6 /* JSON.swift in Sources */, 5CE2BA8B284533A300EC33A6 /* ChatTypes.swift in Sources */, @@ -1879,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1904,7 +1996,8 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; + OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1928,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1953,7 +2046,8 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; + OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1969,11 +2063,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1989,11 +2083,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2014,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2029,7 +2123,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2051,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2066,7 +2160,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2088,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2114,7 +2208,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2139,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2165,7 +2259,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2190,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2205,7 +2299,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2224,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 235; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2239,7 +2333,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.3; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2327,6 +2421,22 @@ version = 5.1.2; }; }; + 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/johnsundell/ink"; + requirement = { + kind = exactVersion; + version = 0.6.0; + }; + }; + B72894592D0C62BF00F7A19A /* XCRemoteSwiftPackageReference "Elegant-Emoji-Picker" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Finalet/Elegant-Emoji-Picker"; + requirement = { + branch = main; + kind = branch; + }; + }; D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/simplex-chat/WebRTC.git"; @@ -2364,6 +2474,16 @@ package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */; productName = Yams; }; + 8CB3476B2CF5CFFA006787A5 /* Ink */ = { + isa = XCSwiftPackageProductDependency; + package = 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */; + productName = Ink; + }; + B728945A2D0C62BF00F7A19A /* ElegantEmojiPicker */ = { + isa = XCSwiftPackageProductDependency; + package = B72894592D0C62BF00F7A19A /* XCRemoteSwiftPackageReference "Elegant-Emoji-Picker" */; + productName = ElegantEmojiPicker; + }; CE38A29B2C3FCD72005ED185 /* SwiftyGif */ = { isa = XCSwiftPackageProductDependency; package = D77B92DA2952372200A5A1CC /* XCRemoteSwiftPackageReference "SwiftyGif" */; diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c8623a95cb..2bddf5b5b8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115", + "originHash" : "07434ae88cbf078ce3d27c91c1f605836aaebff0e0cef5f25317795151c77db1", "pins" : [ { "identity" : "codescanner", @@ -10,6 +10,24 @@ "version" : "2.5.0" } }, + { + "identity" : "elegant-emoji-picker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Finalet/Elegant-Emoji-Picker", + "state" : { + "branch" : "main", + "revision" : "71d2d46092b4d550cc593614efc06438f845f6e6" + } + }, + { + "identity" : "ink", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnsundell/ink", + "state" : { + "revision" : "bcc9f219900a62c4210e6db726035d7f03ae757b", + "version" : "0.6.0" + } + }, { "identity" : "lzstring-swift", "kind" : "remoteSourceControl", diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index 987f7f3d41..0dd3483fd7 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -46,7 +46,7 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio var cConfirm = confirm.rawValue.cString(using: .utf8)! // the last parameter of chat_migrate_init is used to return the pointer to chat controller let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, backgroundMode ? 1 : 0, &chatController)! - let dbRes = dbMigrationResult(fromCString(cjson)) + let dbRes = dbMigrationResult(dataFromCString(cjson)) let encrypted = dbKey != "" let keychainErr = dbRes == .ok && useKeychain && encrypted && !kcDatabasePassword.set(dbKey) let result = (encrypted, keychainErr ? .errorKeychain : dbRes) @@ -63,7 +63,7 @@ public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation var cKey = dbKey.cString(using: .utf8)! var cConfirm = confirmation.rawValue.cString(using: .utf8)! let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, 0, &temporaryController)! - return (dbMigrationResult(fromCString(cjson)), temporaryController) + return (dbMigrationResult(dataFromCString(cjson)), temporaryController) } public func chatInitControllerRemovingDatabases() { @@ -110,27 +110,42 @@ public func resetChatCtrl() { migrationResult = nil } -public func sendSimpleXCmd(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) -> ChatResponse { - var c = cmd.cmdString.cString(using: .utf8)! - let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c)! - return chatResponse(fromCString(cjson)) +@inline(__always) +public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil) -> APIResult { + if let d = sendSimpleXCmdStr(cmd.cmdString, ctrl) { + decodeAPIResult(d) + } else { + APIResult.error(.invalidJSON(json: nil)) + } +} + +@inline(__always) +public func sendSimpleXCmdStr(_ cmd: String, _ ctrl: chat_ctrl? = nil) -> Data? { + var c = cmd.cString(using: .utf8)! + return if let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c) { + dataFromCString(cjson) + } else { + nil + } } // in microseconds public let MESSAGE_TIMEOUT: Int32 = 15_000_000 -public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> ChatResponse? { - if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout) { - let s = fromCString(cjson) - return s == "" ? nil : chatResponse(s) +@inline(__always) +public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> APIResult? { + if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout), + let d = dataFromCString(cjson) { + decodeAPIResult(d) + } else { + nil } - return nil } public func parseSimpleXMarkdown(_ s: String) -> [FormattedText]? { var c = s.cString(using: .utf8)! if let cjson = chat_parse_markdown(&c) { - if let d = fromCString(cjson).data(using: .utf8) { + if let d = dataFromCString(cjson) { do { let r = try jsonDecoder.decode(ParsedMarkdown.self, from: d) return r.formattedText @@ -154,7 +169,7 @@ struct ParsedMarkdown: Decodable { public func parseServerAddress(_ s: String) -> ServerAddress? { var c = s.cString(using: .utf8)! if let cjson = chat_parse_server(&c) { - if let d = fromCString(cjson).data(using: .utf8) { + if let d = dataFromCString(cjson) { do { let r = try jsonDecoder.decode(ParsedServerAddress.self, from: d) return r.serverAddress @@ -171,67 +186,34 @@ struct ParsedServerAddress: Decodable { var parseError: String } +@inline(__always) public func fromCString(_ c: UnsafeMutablePointer) -> String { let s = String.init(cString: c) free(c) return s } -public func chatResponse(_ s: String) -> ChatResponse { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try jsonDecoder.decode(APIResponse.self, from: d) - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") +@inline(__always) +public func dataFromCString(_ c: UnsafeMutablePointer) -> Data? { + let len = strlen(c) + if len > 0 { + return Data(bytesNoCopy: c, count: len, deallocator: .free) + } else { + free(c) + return nil } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "apiChats" { - if let jApiChats = jResp["apiChats"] as? NSDictionary, - let user: UserRef = try? decodeObject(jApiChats["user"] as Any), - let jChats = jApiChats["chats"] as? NSArray { - let chats = jChats.map { jChat in - if let chatData = try? parseChatData(jChat) { - return chatData - } - return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted) ?? "") - } - return .apiChats(user: user, chats: chats) - } - } else if type == "apiChat" { - if let jApiChat = jResp["apiChat"] as? NSDictionary, - let user: UserRef = try? decodeObject(jApiChat["user"] as Any), - let jChat = jApiChat["chat"] as? NSDictionary, - let chat = try? parseChatData(jChat) { - return .apiChat(user: user, chat: chat) - } - } else if type == "chatCmdError" { - if let jError = jResp["chatCmdError"] as? NSDictionary { - return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } else if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return ChatResponse.response(type: type ?? "invalid", json: json ?? s) } -private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { +@inline(__always) +public func dataToString(_ d: Data?) -> String { + if let d { + String(data: d, encoding: .utf8) ?? "invalid string" + } else { + "no data" + } +} + +public func decodeUser_(_ jDict: NSDictionary) -> UserRef? { if let user_ = jDict["user_"] { try? decodeObject(user_ as Any) } else { @@ -239,7 +221,7 @@ private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { } } -private func errorJson(_ jDict: NSDictionary) -> String? { +public func errorJson(_ jDict: NSDictionary) -> Data? { if let chatError = jDict["chatError"] { serializeJSON(chatError) } else { @@ -247,10 +229,15 @@ private func errorJson(_ jDict: NSDictionary) -> String? { } } -func parseChatData(_ jChat: Any) throws -> ChatData { +public func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, NavigationInfo) { let jChatDict = jChat as! NSDictionary let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!) let chatStats: ChatStats = try decodeObject(jChatDict["chatStats"]!) + let navInfo: NavigationInfo = if let jNavInfo = jNavInfo as? NSDictionary, let jNav = jNavInfo["navInfo"] { + try decodeObject(jNav) + } else { + NavigationInfo() + } let jChatItems = jChatDict["chatItems"] as! NSArray let chatItems = jChatItems.map { jCI in if let ci: ChatItem = try? decodeObject(jCI) { @@ -259,16 +246,18 @@ func parseChatData(_ jChat: Any) throws -> ChatData { return ChatItem.invalidJSON( chatDir: decodeProperty(jCI, "chatDir"), meta: decodeProperty(jCI, "meta"), - json: serializeJSON(jCI, options: .prettyPrinted) ?? "" + json: serializeJSON(jCI, options: .prettyPrinted) ) } - return ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats) + return (ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats), navInfo) } -func decodeObject(_ obj: Any) throws -> T { +@inline(__always) +public func decodeObject(_ obj: Any) throws -> T { try jsonDecoder.decode(T.self, from: JSONSerialization.data(withJSONObject: obj)) } +@inline(__always) func decodeProperty(_ obj: Any, _ prop: NSString) -> T? { if let jProp = (obj as? NSDictionary)?[prop] { return try? decodeObject(jProp) @@ -276,28 +265,52 @@ func decodeProperty(_ obj: Any, _ prop: NSString) -> T? { return nil } -func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? { - if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) { - return String(decoding: d, as: UTF8.self) +@inline(__always) +func getOWSF(_ obj: NSDictionary, _ prop: NSString) -> (type: String, object: NSDictionary)? { + if let j = obj[prop] as? NSDictionary, j.count == 1 || j.count == 2 { + var type = j.allKeys[0] as? String + if j.count == 2 && type == "_owsf" { + type = j.allKeys[1] as? String + } + if let type { + return (type, j) + } } return nil } -public func responseError(_ err: Error) -> String { - if let r = err as? ChatResponse { - switch r { - case let .chatCmdError(_, chatError): return chatErrorString(chatError) - case let .chatError(_, chatError): return chatErrorString(chatError) - default: return "\(String(describing: r.responseType)), details: \(String(describing: r.details))" - } +@inline(__always) +public func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> Data? { + if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) { + dataPrefix(d) } else { - return String(describing: err) + nil } } -func chatErrorString(_ err: ChatError) -> String { - if case let .invalidJSON(json) = err { return json } - return String(describing: err) +let MAX_JSON_VIEW_LENGTH = 2048 + +@inline(__always) +public func dataPrefix(_ d: Data) -> Data { + d.count > MAX_JSON_VIEW_LENGTH + ? Data(d.prefix(MAX_JSON_VIEW_LENGTH)) + : d +} + +public func responseError(_ err: Error) -> String { + if let e = err as? ChatError { + chatErrorString(e) + } else { + String(describing: err) + } +} + +public func chatErrorString(_ err: ChatError) -> String { + switch err { + case let .invalidJSON(json): dataToString(json) + case let .unexpectedResult(type): "unexpected result: \(type)" + default: String(describing: err) + } } public enum DBMigrationResult: Decodable, Equatable { @@ -336,15 +349,15 @@ public enum MTRError: Decodable, Equatable { case different(appMigration: String, dbMigration: String) } -func dbMigrationResult(_ s: String) -> DBMigrationResult { - let d = s.data(using: .utf8)! -// TODO is there a way to do it without copying the data? e.g: -// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) -// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - return try jsonDecoder.decode(DBMigrationResult.self, from: d) - } catch let error { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - return .unknown(json: s) +func dbMigrationResult(_ d: Data?) -> DBMigrationResult { + if let d { + do { + return try jsonDecoder.decode(DBMigrationResult.self, from: d) + } catch let error { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + return .unknown(json: dataToString(d)) + } + } else { + return .unknown(json: "no data") } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a6409dec2f..b8d2361ac8 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -8,1271 +8,188 @@ import Foundation import SwiftUI +import Network public let jsonDecoder = getJSONDecoder() public let jsonEncoder = getJSONEncoder() -public enum ChatCommand { - case showActiveUser - case createActiveUser(profile: Profile?, pastTimestamp: Bool) - case listUsers - case apiSetActiveUser(userId: Int64, viewPwd: String?) - case setAllContactReceipts(enable: Bool) - case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) - case apiSetUserGroupReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) - case apiHideUser(userId: Int64, viewPwd: String) - case apiUnhideUser(userId: Int64, viewPwd: String) - case apiMuteUser(userId: Int64) - case apiUnmuteUser(userId: Int64) - case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?) - case startChat(mainApp: Bool, enableSndFiles: Bool) - case checkChatRunning - case apiStopChat - case apiActivateChat(restoreChat: Bool) - case apiSuspendChat(timeoutMicroseconds: Int) - case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) - case apiSetEncryptLocalFiles(enable: Bool) - case apiExportArchive(config: ArchiveConfig) - case apiImportArchive(config: ArchiveConfig) - case apiDeleteStorage - case apiStorageEncryption(config: DBEncryptionConfig) - case testStorageEncryption(key: String) - case apiSaveSettings(settings: AppSettings) - case apiGetSettings(settings: AppSettings) - case apiGetChats(userId: Int64) - case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String) - case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) - case apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?) - case apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) - case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool) - case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) - case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) - case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) - case apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) - case apiGetNtfToken - case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) - case apiVerifyToken(token: DeviceToken, nonce: String, code: String) - case apiDeleteToken(token: DeviceToken) - case apiGetNtfMessage(nonce: String, encNtfInfo: String) - case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) - case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) - case apiJoinGroup(groupId: Int64) - case apiMemberRole(groupId: Int64, memberId: Int64, memberRole: GroupMemberRole) - case apiBlockMemberForAll(groupId: Int64, memberId: Int64, blocked: Bool) - case apiRemoveMember(groupId: Int64, memberId: Int64) - case apiLeaveGroup(groupId: Int64) - case apiListMembers(groupId: Int64) - case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) - case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole) - case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) - case apiDeleteGroupLink(groupId: Int64) - case apiGetGroupLink(groupId: Int64) - case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) - case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) - case apiGetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol) - case apiSetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol, servers: [ServerCfg]) - case apiTestProtoServer(userId: Int64, server: String) - case apiSetChatItemTTL(userId: Int64, seconds: Int64?) - case apiGetChatItemTTL(userId: Int64) - case apiSetNetworkConfig(networkConfig: NetCfg) - case apiGetNetworkConfig - case apiSetNetworkInfo(networkInfo: UserNetworkInfo) - case reconnectAllServers - case reconnectServer(userId: Int64, smpServer: String) - case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) - case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings) - case apiContactInfo(contactId: Int64) - case apiGroupMemberInfo(groupId: Int64, groupMemberId: Int64) - case apiContactQueueInfo(contactId: Int64) - case apiGroupMemberQueueInfo(groupId: Int64, groupMemberId: Int64) - case apiSwitchContact(contactId: Int64) - case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64) - case apiAbortSwitchContact(contactId: Int64) - case apiAbortSwitchGroupMember(groupId: Int64, groupMemberId: Int64) - case apiSyncContactRatchet(contactId: Int64, force: Bool) - case apiSyncGroupMemberRatchet(groupId: Int64, groupMemberId: Int64, force: Bool) - case apiGetContactCode(contactId: Int64) - case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) - case apiVerifyContact(contactId: Int64, connectionCode: String?) - case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) - case apiAddContact(userId: Int64, incognito: Bool) - case apiSetConnectionIncognito(connId: Int64, incognito: Bool) - case apiConnectPlan(userId: Int64, connReq: String) - case apiConnect(userId: Int64, incognito: Bool, connReq: String) - case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) - case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) - case apiClearChat(type: ChatType, id: Int64) - case apiListContacts(userId: Int64) - case apiUpdateProfile(userId: Int64, profile: Profile) - case apiSetContactPrefs(contactId: Int64, preferences: Preferences) - case apiSetContactAlias(contactId: Int64, localAlias: String) - case apiSetConnectionAlias(connId: Int64, localAlias: String) - case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) - case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?) - case apiCreateMyAddress(userId: Int64) - case apiDeleteMyAddress(userId: Int64) - case apiShowMyAddress(userId: Int64) - case apiSetProfileAddress(userId: Int64, on: Bool) - case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) - case apiAcceptContact(incognito: Bool, contactReqId: Int64) - case apiRejectContact(contactReqId: Int64) - // WebRTC calls - case apiSendCallInvitation(contact: Contact, callType: CallType) - case apiRejectCall(contact: Contact) - case apiSendCallOffer(contact: Contact, callOffer: WebRTCCallOffer) - case apiSendCallAnswer(contact: Contact, answer: WebRTCSession) - case apiSendCallExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo) - case apiEndCall(contact: Contact) - case apiGetCallInvitations - case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) - // WebRTC calls / - case apiGetNetworkStatuses - case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) - case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) - case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) - case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) - case cancelFile(fileId: Int64) - // remote desktop commands - case setLocalDeviceName(displayName: String) - case connectRemoteCtrl(xrcpInvitation: String) - case findKnownRemoteCtrl - case confirmRemoteCtrl(remoteCtrlId: Int64) - case verifyRemoteCtrlSession(sessionCode: String) - case listRemoteCtrls - case stopRemoteCtrl - case deleteRemoteCtrl(remoteCtrlId: Int64) - case apiUploadStandaloneFile(userId: Int64, file: CryptoFile) - case apiDownloadStandaloneFile(userId: Int64, url: String, file: CryptoFile) - case apiStandaloneFileInfo(url: String) - // misc - case showVersion - case getAgentSubsTotal(userId: Int64) - case getAgentServersSummary(userId: Int64) - case resetAgentServersStats - case string(String) - - public var cmdString: String { - get { - switch self { - case .showActiveUser: return "/u" - case let .createActiveUser(profile, pastTimestamp): - let user = NewUser(profile: profile, pastTimestamp: pastTimestamp) - return "/_create user \(encodeJSON(user))" - case .listUsers: return "/users" - case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))" - case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))" - case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings): - let umrs = userMsgReceiptSettings - return "/_set receipts contacts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" - case let .apiSetUserGroupReceipts(userId, userMsgReceiptSettings): - let umrs = userMsgReceiptSettings - return "/_set receipts groups \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" - case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))" - case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))" - case let .apiMuteUser(userId): return "/_mute user \(userId)" - case let .apiUnmuteUser(userId): return "/_unmute user \(userId)" - case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))" - case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" - case .checkChatRunning: return "/_check running" - case .apiStopChat: return "/_stop" - case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" - case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" - case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" - case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" - case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))" - case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" - case .apiDeleteStorage: return "/_db delete" - case let .apiStorageEncryption(cfg): return "/_db encryption \(encodeJSON(cfg))" - case let .testStorageEncryption(key): return "/db test key \(key)" - case let .apiSaveSettings(settings): return "/_save app settings \(encodeJSON(settings))" - case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))" - case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" - case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + - (search == "" ? "" : " search=\(search)") - case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" - case let .apiSendMessage(type, id, file, quotedItemId, mc, live, ttl): - let msg = encodeJSON(ComposedMessage(fileSource: file, quotedItemId: quotedItemId, msgContent: mc)) - let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msg)" - case let .apiCreateChatItem(noteFolderId, file, mc): - let msg = encodeJSON(ComposedMessage(fileSource: file, msgContent: mc)) - return "/_create *\(noteFolderId) json \(msg)" - case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)" - case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" - case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" - case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" - case let .apiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl): - let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemId) ttl=\(ttlStr)" - case .apiGetNtfToken: return "/_ntf get " - case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" - case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" - case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" - case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)" - case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))" - case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)" - case let .apiJoinGroup(groupId): return "/_join #\(groupId)" - case let .apiMemberRole(groupId, memberId, memberRole): return "/_member role #\(groupId) \(memberId) \(memberRole.rawValue)" - case let .apiBlockMemberForAll(groupId, memberId, blocked): return "/_block #\(groupId) \(memberId) blocked=\(onOff(blocked))" - case let .apiRemoveMember(groupId, memberId): return "/_remove #\(groupId) \(memberId)" - case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" - case let .apiListMembers(groupId): return "/_members #\(groupId)" - case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" - case let .apiCreateGroupLink(groupId, memberRole): return "/_create link #\(groupId) \(memberRole)" - case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" - case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" - case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" - case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" - case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" - case let .apiGetUserProtoServers(userId, serverProtocol): return "/_servers \(userId) \(serverProtocol)" - case let .apiSetUserProtoServers(userId, serverProtocol, servers): return "/_servers \(userId) \(serverProtocol) \(protoServersStr(servers))" - case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" - case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" - case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" - case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" - case .apiGetNetworkConfig: return "/network" - case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))" - case .reconnectAllServers: return "/reconnect" - case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)" - case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))" - case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))" - case let .apiContactInfo(contactId): return "/_info @\(contactId)" - case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)" - case let .apiContactQueueInfo(contactId): return "/_queue info @\(contactId)" - case let .apiGroupMemberQueueInfo(groupId, groupMemberId): return "/_queue info #\(groupId) \(groupMemberId)" - case let .apiSwitchContact(contactId): return "/_switch @\(contactId)" - case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)" - case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)" - case let .apiAbortSwitchGroupMember(groupId, groupMemberId): return "/_abort switch #\(groupId) \(groupMemberId)" - case let .apiSyncContactRatchet(contactId, force): if force { - return "/_sync @\(contactId) force=on" - } else { - return "/_sync @\(contactId)" - } - case let .apiSyncGroupMemberRatchet(groupId, groupMemberId, force): if force { - return "/_sync #\(groupId) \(groupMemberId) force=on" - } else { - return "/_sync #\(groupId) \(groupMemberId)" - } - case let .apiGetContactCode(contactId): return "/_get code @\(contactId)" - case let .apiGetGroupMemberCode(groupId, groupMemberId): return "/_get code #\(groupId) \(groupMemberId)" - case let .apiVerifyContact(contactId, .some(connectionCode)): return "/_verify code @\(contactId) \(connectionCode)" - case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)" - case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)" - case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" - case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))" - case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" - case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)" - case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)" - case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" - case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)" - case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" - case let .apiListContacts(userId): return "/_contacts \(userId)" - case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" - case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))" - case let .apiSetContactAlias(contactId, localAlias): return "/_set alias @\(contactId) \(localAlias.trimmingCharacters(in: .whitespaces))" - case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" - case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")" - case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")" - case let .apiCreateMyAddress(userId): return "/_address \(userId)" - case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" - case let .apiShowMyAddress(userId): return "/_show_address \(userId)" - case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" - case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" - case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" - case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" - case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))" - case let .apiRejectCall(contact): return "/_call reject @\(contact.apiId)" - case let .apiSendCallOffer(contact, callOffer): return "/_call offer @\(contact.apiId) \(encodeJSON(callOffer))" - case let .apiSendCallAnswer(contact, answer): return "/_call answer @\(contact.apiId) \(encodeJSON(answer))" - case let .apiSendCallExtraInfo(contact, extraInfo): return "/_call extra @\(contact.apiId) \(encodeJSON(extraInfo))" - case let .apiEndCall(contact): return "/_call end @\(contact.apiId)" - case .apiGetCallInvitations: return "/_call get" - case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" - case .apiGetNetworkStatuses: return "/_network_statuses" - case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" - case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" - case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" - case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" - case let .cancelFile(fileId): return "/fcancel \(fileId)" - case let .setLocalDeviceName(displayName): return "/set device name \(displayName)" - case let .connectRemoteCtrl(xrcpInv): return "/connect remote ctrl \(xrcpInv)" - case .findKnownRemoteCtrl: return "/find remote ctrl" - case let .confirmRemoteCtrl(rcId): return "/confirm remote ctrl \(rcId)" - case let .verifyRemoteCtrlSession(sessCode): return "/verify remote ctrl \(sessCode)" - case .listRemoteCtrls: return "/list remote ctrls" - case .stopRemoteCtrl: return "/stop remote ctrl" - case let .deleteRemoteCtrl(rcId): return "/delete remote ctrl \(rcId)" - case let .apiUploadStandaloneFile(userId, file): return "/_upload \(userId) \(file.filePath)" - case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" - case let .apiStandaloneFileInfo(link): return "/_download info \(link)" - case .showVersion: return "/version" - case let .getAgentSubsTotal(userId): return "/get subs total \(userId)" - case let .getAgentServersSummary(userId): return "/get servers summary \(userId)" - case .resetAgentServersStats: return "/reset servers stats" - case let .string(str): return str - } - } - } - - public var cmdType: String { - get { - switch self { - case .showActiveUser: return "showActiveUser" - case .createActiveUser: return "createActiveUser" - case .listUsers: return "listUsers" - case .apiSetActiveUser: return "apiSetActiveUser" - case .setAllContactReceipts: return "setAllContactReceipts" - case .apiSetUserContactReceipts: return "apiSetUserContactReceipts" - case .apiSetUserGroupReceipts: return "apiSetUserGroupReceipts" - case .apiHideUser: return "apiHideUser" - case .apiUnhideUser: return "apiUnhideUser" - case .apiMuteUser: return "apiMuteUser" - case .apiUnmuteUser: return "apiUnmuteUser" - case .apiDeleteUser: return "apiDeleteUser" - case .startChat: return "startChat" - case .checkChatRunning: return "checkChatRunning" - case .apiStopChat: return "apiStopChat" - case .apiActivateChat: return "apiActivateChat" - case .apiSuspendChat: return "apiSuspendChat" - case .apiSetAppFilePaths: return "apiSetAppFilePaths" - case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles" - case .apiExportArchive: return "apiExportArchive" - case .apiImportArchive: return "apiImportArchive" - case .apiDeleteStorage: return "apiDeleteStorage" - case .apiStorageEncryption: return "apiStorageEncryption" - case .testStorageEncryption: return "testStorageEncryption" - case .apiSaveSettings: return "apiSaveSettings" - case .apiGetSettings: return "apiGetSettings" - case .apiGetChats: return "apiGetChats" - case .apiGetChat: return "apiGetChat" - case .apiGetChatItemInfo: return "apiGetChatItemInfo" - case .apiSendMessage: return "apiSendMessage" - case .apiCreateChatItem: return "apiCreateChatItem" - case .apiUpdateChatItem: return "apiUpdateChatItem" - case .apiDeleteChatItem: return "apiDeleteChatItem" - case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" - case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" - case .apiChatItemReaction: return "apiChatItemReaction" - case .apiForwardChatItem: return "apiForwardChatItem" - case .apiGetNtfToken: return "apiGetNtfToken" - case .apiRegisterToken: return "apiRegisterToken" - case .apiVerifyToken: return "apiVerifyToken" - case .apiDeleteToken: return "apiDeleteToken" - case .apiGetNtfMessage: return "apiGetNtfMessage" - case .apiNewGroup: return "apiNewGroup" - case .apiAddMember: return "apiAddMember" - case .apiJoinGroup: return "apiJoinGroup" - case .apiMemberRole: return "apiMemberRole" - case .apiBlockMemberForAll: return "apiBlockMemberForAll" - case .apiRemoveMember: return "apiRemoveMember" - case .apiLeaveGroup: return "apiLeaveGroup" - case .apiListMembers: return "apiListMembers" - case .apiUpdateGroupProfile: return "apiUpdateGroupProfile" - case .apiCreateGroupLink: return "apiCreateGroupLink" - case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" - case .apiDeleteGroupLink: return "apiDeleteGroupLink" - case .apiGetGroupLink: return "apiGetGroupLink" - case .apiCreateMemberContact: return "apiCreateMemberContact" - case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" - case .apiGetUserProtoServers: return "apiGetUserProtoServers" - case .apiSetUserProtoServers: return "apiSetUserProtoServers" - case .apiTestProtoServer: return "apiTestProtoServer" - case .apiSetChatItemTTL: return "apiSetChatItemTTL" - case .apiGetChatItemTTL: return "apiGetChatItemTTL" - case .apiSetNetworkConfig: return "apiSetNetworkConfig" - case .apiGetNetworkConfig: return "apiGetNetworkConfig" - case .apiSetNetworkInfo: return "apiSetNetworkInfo" - case .reconnectAllServers: return "reconnectAllServers" - case .reconnectServer: return "reconnectServer" - case .apiSetChatSettings: return "apiSetChatSettings" - case .apiSetMemberSettings: return "apiSetMemberSettings" - case .apiContactInfo: return "apiContactInfo" - case .apiGroupMemberInfo: return "apiGroupMemberInfo" - case .apiContactQueueInfo: return "apiContactQueueInfo" - case .apiGroupMemberQueueInfo: return "apiGroupMemberQueueInfo" - case .apiSwitchContact: return "apiSwitchContact" - case .apiSwitchGroupMember: return "apiSwitchGroupMember" - case .apiAbortSwitchContact: return "apiAbortSwitchContact" - case .apiAbortSwitchGroupMember: return "apiAbortSwitchGroupMember" - case .apiSyncContactRatchet: return "apiSyncContactRatchet" - case .apiSyncGroupMemberRatchet: return "apiSyncGroupMemberRatchet" - case .apiGetContactCode: return "apiGetContactCode" - case .apiGetGroupMemberCode: return "apiGetGroupMemberCode" - case .apiVerifyContact: return "apiVerifyContact" - case .apiVerifyGroupMember: return "apiVerifyGroupMember" - case .apiAddContact: return "apiAddContact" - case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" - case .apiConnectPlan: return "apiConnectPlan" - case .apiConnect: return "apiConnect" - case .apiDeleteChat: return "apiDeleteChat" - case .apiClearChat: return "apiClearChat" - case .apiListContacts: return "apiListContacts" - case .apiUpdateProfile: return "apiUpdateProfile" - case .apiSetContactPrefs: return "apiSetContactPrefs" - case .apiSetContactAlias: return "apiSetContactAlias" - case .apiSetConnectionAlias: return "apiSetConnectionAlias" - case .apiSetUserUIThemes: return "apiSetUserUIThemes" - case .apiSetChatUIThemes: return "apiSetChatUIThemes" - case .apiCreateMyAddress: return "apiCreateMyAddress" - case .apiDeleteMyAddress: return "apiDeleteMyAddress" - case .apiShowMyAddress: return "apiShowMyAddress" - case .apiSetProfileAddress: return "apiSetProfileAddress" - case .apiAddressAutoAccept: return "apiAddressAutoAccept" - case .apiAcceptContact: return "apiAcceptContact" - case .apiRejectContact: return "apiRejectContact" - case .apiSendCallInvitation: return "apiSendCallInvitation" - case .apiRejectCall: return "apiRejectCall" - case .apiSendCallOffer: return "apiSendCallOffer" - case .apiSendCallAnswer: return "apiSendCallAnswer" - case .apiSendCallExtraInfo: return "apiSendCallExtraInfo" - case .apiEndCall: return "apiEndCall" - case .apiGetCallInvitations: return "apiGetCallInvitations" - case .apiCallStatus: return "apiCallStatus" - case .apiGetNetworkStatuses: return "apiGetNetworkStatuses" - case .apiChatRead: return "apiChatRead" - case .apiChatUnread: return "apiChatUnread" - case .receiveFile: return "receiveFile" - case .setFileToReceive: return "setFileToReceive" - case .cancelFile: return "cancelFile" - case .setLocalDeviceName: return "setLocalDeviceName" - case .connectRemoteCtrl: return "connectRemoteCtrl" - case .findKnownRemoteCtrl: return "findKnownRemoteCtrl" - case .confirmRemoteCtrl: return "confirmRemoteCtrl" - case .verifyRemoteCtrlSession: return "verifyRemoteCtrlSession" - case .listRemoteCtrls: return "listRemoteCtrls" - case .stopRemoteCtrl: return "stopRemoteCtrl" - case .deleteRemoteCtrl: return "deleteRemoteCtrl" - case .apiUploadStandaloneFile: return "apiUploadStandaloneFile" - case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" - case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" - case .showVersion: return "showVersion" - case .getAgentSubsTotal: return "getAgentSubsTotal" - case .getAgentServersSummary: return "getAgentServersSummary" - case .resetAgentServersStats: return "resetAgentServersStats" - case .string: return "console command" - } - } - } - - func ref(_ type: ChatType, _ id: Int64) -> String { - "\(type.rawValue)\(id)" - } - - func protoServersStr(_ servers: [ServerCfg]) -> String { - encodeJSON(ProtoServersConfig(servers: servers)) - } - - func chatItemTTLStr(seconds: Int64?) -> String { - if let seconds = seconds { - return String(seconds) - } else { - return "none" - } - } - - public var obfuscated: ChatCommand { - switch self { - case let .apiStorageEncryption(cfg): - return .apiStorageEncryption(config: DBEncryptionConfig(currentKey: obfuscate(cfg.currentKey), newKey: obfuscate(cfg.newKey))) - case let .apiSetActiveUser(userId, viewPwd): - return .apiSetActiveUser(userId: userId, viewPwd: obfuscate(viewPwd)) - case let .apiHideUser(userId, viewPwd): - return .apiHideUser(userId: userId, viewPwd: obfuscate(viewPwd)) - case let .apiUnhideUser(userId, viewPwd): - return .apiUnhideUser(userId: userId, viewPwd: obfuscate(viewPwd)) - case let .apiDeleteUser(userId, delSMPQueues, viewPwd): - return .apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: obfuscate(viewPwd)) - case let .testStorageEncryption(key): - return .testStorageEncryption(key: obfuscate(key)) - default: return self - } - } - - private func obfuscate(_ s: String) -> String { - s == "" ? "" : "***" - } - - private func obfuscate(_ s: String?) -> String? { - if let s = s { - return obfuscate(s) - } - return nil - } - - private func onOffParam(_ param: String, _ b: Bool?) -> String { - if let b = b { - return " \(param)=\(onOff(b))" - } - return "" - } - - private func maybePwd(_ pwd: String?) -> String { - pwd == "" || pwd == nil ? "" : " " + encodeJSON(pwd) - } +public protocol ChatCmdProtocol { + var cmdString: String { get } } -private func onOff(_ b: Bool) -> String { +@inline(__always) +public func onOff(_ b: Bool) -> String { b ? "on" : "off" } -public struct APIResponse: Decodable { - var resp: ChatResponse -} - -public enum ChatResponse: Decodable, Error { - case response(type: String, json: String) - case activeUser(user: User) - case usersList(users: [UserInfo]) - case chatStarted - case chatRunning - case chatStopped - case chatSuspended - case apiChats(user: UserRef, chats: [ChatData]) - case apiChat(user: UserRef, chat: ChatData) - case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) - case userProtoServers(user: UserRef, servers: UserProtoServers) - case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) - case chatItemTTL(user: UserRef, chatItemTTL: Int64?) - case networkConfig(networkConfig: NetCfg) - case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) - case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) - case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: QueueInfo) - case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) - case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) - case groupMemberSwitchAborted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) - case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) - case contactRatchetSyncStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) - case groupMemberRatchetSyncStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactRatchetSync(user: UserRef, contact: Contact, ratchetSyncProgress: RatchetSyncProgress) - case groupMemberRatchetSync(user: UserRef, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress) - case contactVerificationReset(user: UserRef, contact: Contact) - case groupMemberVerificationReset(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case contactCode(user: UserRef, contact: Contact, connectionCode: String) - case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) - case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) - case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection) - case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) - case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan) - case sentConfirmation(user: UserRef, connection: PendingContactConnection) - case sentInvitation(user: UserRef, connection: PendingContactConnection) - case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) - case contactAlreadyExists(user: UserRef, contact: Contact) - case contactRequestAlreadyAccepted(user: UserRef, contact: Contact) - case contactDeleted(user: UserRef, contact: Contact) - case contactDeletedByContact(user: UserRef, contact: Contact) - case chatCleared(user: UserRef, chatInfo: ChatInfo) - case userProfileNoChange(user: User) - case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) - case userPrivacy(user: User, updatedUser: User) - case contactAliasUpdated(user: UserRef, toContact: Contact) - case connectionAliasUpdated(user: UserRef, toConnection: PendingContactConnection) - case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact) - case userContactLink(user: User, contactLink: UserContactLink) - case userContactLinkUpdated(user: User, contactLink: UserContactLink) - case userContactLinkCreated(user: User, connReqContact: String) - case userContactLinkDeleted(user: User) - case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) - case contactConnecting(user: UserRef, contact: Contact) - case contactSndReady(user: UserRef, contact: Contact) - case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) - case acceptingContactRequest(user: UserRef, contact: Contact) - case contactRequestRejected(user: UserRef) - case contactUpdated(user: UserRef, toContact: Contact) - case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember) - case networkStatus(networkStatus: NetworkStatus, connections: [String]) - case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) - case groupSubscribed(user: UserRef, groupInfo: GroupRef) - case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError]) - case groupEmpty(user: UserRef, groupInfo: GroupInfo) - case userContactLinkSubscribed - case newChatItem(user: UserRef, chatItem: AChatItem) - case chatItemStatusUpdated(user: UserRef, chatItem: AChatItem) - case chatItemUpdated(user: UserRef, chatItem: AChatItem) - case chatItemNotChanged(user: UserRef, chatItem: AChatItem) - case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) - case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) - case contactsList(user: UserRef, contacts: [Contact]) - // group events - case groupCreated(user: UserRef, groupInfo: GroupInfo) - case sentGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, member: GroupMember) - case userAcceptedGroupSent(user: UserRef, groupInfo: GroupInfo, hostContact: Contact?) - case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember) - case userDeletedMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case leftMemberUser(user: UserRef, groupInfo: GroupInfo) - case groupMembers(user: UserRef, group: Group) - case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) - case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) - case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) - case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) - case memberRoleUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) - case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) - case memberBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, blocked: Bool) - case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember) - case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) - case groupInvitation(user: UserRef, groupInfo: GroupInfo) // unused - case userJoinedGroup(user: UserRef, groupInfo: GroupInfo) - case joinedGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) - case groupRemoved(user: UserRef, groupInfo: GroupInfo) // unused - case groupUpdated(user: UserRef, toGroup: GroupInfo) - case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole) - case groupLink(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole) - case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) - case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - // receiving file events - case rcvFileAccepted(user: UserRef, chatItem: AChatItem) - case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) - case standaloneFileInfo(fileMeta: MigrationFileLinkData?) - case rcvStandaloneFileCreated(user: UserRef, rcvFileTransfer: RcvFileTransfer) - case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats - case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) - case rcvFileComplete(user: UserRef, chatItem: AChatItem) - case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) - case rcvFileCancelled(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) - case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) - case rcvFileError(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) - case rcvFileWarning(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) - // sending file events - case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) - case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) - case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload - case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used - case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) - case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) - case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) - case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) - case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) - case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - // call events - case callInvitation(callInvitation: RcvCallInvitation) - case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) - case callAnswer(user: UserRef, contact: Contact, answer: WebRTCSession) - case callExtraInfo(user: UserRef, contact: Contact, extraInfo: WebRTCExtraInfo) - case callEnded(user: UserRef, contact: Contact) - case callInvitations(callInvitations: [RcvCallInvitation]) - case ntfTokenStatus(status: NtfTknStatus) - case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) - case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessage_: NtfMsgInfo?) - case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgInfo) - case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) - case contactDisabled(user: UserRef, contact: Contact) - // remote desktop responses/events - case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo]) - case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool) - case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String) - case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String) - case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo) - case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) - // pq - case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) - // misc - case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) - case cmdOk(user: UserRef?) - case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) - case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) - case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) - case chatCmdError(user_: UserRef?, chatError: ChatError) - case chatError(user_: UserRef?, chatError: ChatError) - case archiveExported(archiveErrors: [ArchiveError]) - case archiveImported(archiveErrors: [ArchiveError]) - case appSettings(appSettings: AppSettings) - +public enum APIResult: Decodable where R: Decodable, R: ChatAPIResult { + case result(R) + case error(ChatError) + case invalid(type: String, json: Data) + public var responseType: String { - get { - switch self { - case let .response(type, _): return "* \(type)" - case .activeUser: return "activeUser" - case .usersList: return "usersList" - case .chatStarted: return "chatStarted" - case .chatRunning: return "chatRunning" - case .chatStopped: return "chatStopped" - case .chatSuspended: return "chatSuspended" - case .apiChats: return "apiChats" - case .apiChat: return "apiChat" - case .chatItemInfo: return "chatItemInfo" - case .userProtoServers: return "userProtoServers" - case .serverTestResult: return "serverTestResult" - case .chatItemTTL: return "chatItemTTL" - case .networkConfig: return "networkConfig" - case .contactInfo: return "contactInfo" - case .groupMemberInfo: return "groupMemberInfo" - case .queueInfo: return "queueInfo" - case .contactSwitchStarted: return "contactSwitchStarted" - case .groupMemberSwitchStarted: return "groupMemberSwitchStarted" - case .contactSwitchAborted: return "contactSwitchAborted" - case .groupMemberSwitchAborted: return "groupMemberSwitchAborted" - case .contactSwitch: return "contactSwitch" - case .groupMemberSwitch: return "groupMemberSwitch" - case .contactRatchetSyncStarted: return "contactRatchetSyncStarted" - case .groupMemberRatchetSyncStarted: return "groupMemberRatchetSyncStarted" - case .contactRatchetSync: return "contactRatchetSync" - case .groupMemberRatchetSync: return "groupMemberRatchetSync" - case .contactVerificationReset: return "contactVerificationReset" - case .groupMemberVerificationReset: return "groupMemberVerificationReset" - case .contactCode: return "contactCode" - case .groupMemberCode: return "groupMemberCode" - case .connectionVerified: return "connectionVerified" - case .invitation: return "invitation" - case .connectionIncognitoUpdated: return "connectionIncognitoUpdated" - case .connectionPlan: return "connectionPlan" - case .sentConfirmation: return "sentConfirmation" - case .sentInvitation: return "sentInvitation" - case .sentInvitationToContact: return "sentInvitationToContact" - case .contactAlreadyExists: return "contactAlreadyExists" - case .contactRequestAlreadyAccepted: return "contactRequestAlreadyAccepted" - case .contactDeleted: return "contactDeleted" - case .contactDeletedByContact: return "contactDeletedByContact" - case .chatCleared: return "chatCleared" - case .userProfileNoChange: return "userProfileNoChange" - case .userProfileUpdated: return "userProfileUpdated" - case .userPrivacy: return "userPrivacy" - case .contactAliasUpdated: return "contactAliasUpdated" - case .connectionAliasUpdated: return "connectionAliasUpdated" - case .contactPrefsUpdated: return "contactPrefsUpdated" - case .userContactLink: return "userContactLink" - case .userContactLinkUpdated: return "userContactLinkUpdated" - case .userContactLinkCreated: return "userContactLinkCreated" - case .userContactLinkDeleted: return "userContactLinkDeleted" - case .contactConnected: return "contactConnected" - case .contactConnecting: return "contactConnecting" - case .contactSndReady: return "contactSndReady" - case .receivedContactRequest: return "receivedContactRequest" - case .acceptingContactRequest: return "acceptingContactRequest" - case .contactRequestRejected: return "contactRequestRejected" - case .contactUpdated: return "contactUpdated" - case .groupMemberUpdated: return "groupMemberUpdated" - case .networkStatus: return "networkStatus" - case .networkStatuses: return "networkStatuses" - case .groupSubscribed: return "groupSubscribed" - case .memberSubErrors: return "memberSubErrors" - case .groupEmpty: return "groupEmpty" - case .userContactLinkSubscribed: return "userContactLinkSubscribed" - case .newChatItem: return "newChatItem" - case .chatItemStatusUpdated: return "chatItemStatusUpdated" - case .chatItemUpdated: return "chatItemUpdated" - case .chatItemNotChanged: return "chatItemNotChanged" - case .chatItemReaction: return "chatItemReaction" - case .chatItemsDeleted: return "chatItemsDeleted" - case .contactsList: return "contactsList" - case .groupCreated: return "groupCreated" - case .sentGroupInvitation: return "sentGroupInvitation" - case .userAcceptedGroupSent: return "userAcceptedGroupSent" - case .groupLinkConnecting: return "groupLinkConnecting" - case .userDeletedMember: return "userDeletedMember" - case .leftMemberUser: return "leftMemberUser" - case .groupMembers: return "groupMembers" - case .receivedGroupInvitation: return "receivedGroupInvitation" - case .groupDeletedUser: return "groupDeletedUser" - case .joinedGroupMemberConnecting: return "joinedGroupMemberConnecting" - case .memberRole: return "memberRole" - case .memberRoleUser: return "memberRoleUser" - case .memberBlockedForAll: return "memberBlockedForAll" - case .memberBlockedForAllUser: return "memberBlockedForAllUser" - case .deletedMemberUser: return "deletedMemberUser" - case .deletedMember: return "deletedMember" - case .leftMember: return "leftMember" - case .groupDeleted: return "groupDeleted" - case .contactsMerged: return "contactsMerged" - case .groupInvitation: return "groupInvitation" - case .userJoinedGroup: return "userJoinedGroup" - case .joinedGroupMember: return "joinedGroupMember" - case .connectedToGroupMember: return "connectedToGroupMember" - case .groupRemoved: return "groupRemoved" - case .groupUpdated: return "groupUpdated" - case .groupLinkCreated: return "groupLinkCreated" - case .groupLink: return "groupLink" - case .groupLinkDeleted: return "groupLinkDeleted" - case .newMemberContact: return "newMemberContact" - case .newMemberContactSentInv: return "newMemberContactSentInv" - case .newMemberContactReceivedInv: return "newMemberContactReceivedInv" - case .rcvFileAccepted: return "rcvFileAccepted" - case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" - case .standaloneFileInfo: return "standaloneFileInfo" - case .rcvStandaloneFileCreated: return "rcvStandaloneFileCreated" - case .rcvFileStart: return "rcvFileStart" - case .rcvFileProgressXFTP: return "rcvFileProgressXFTP" - case .rcvFileComplete: return "rcvFileComplete" - case .rcvStandaloneFileComplete: return "rcvStandaloneFileComplete" - case .rcvFileCancelled: return "rcvFileCancelled" - case .rcvFileSndCancelled: return "rcvFileSndCancelled" - case .rcvFileError: return "rcvFileError" - case .rcvFileWarning: return "rcvFileWarning" - case .sndFileStart: return "sndFileStart" - case .sndFileComplete: return "sndFileComplete" - case .sndFileCancelled: return "sndFileCancelled" - case .sndStandaloneFileCreated: return "sndStandaloneFileCreated" - case .sndFileStartXFTP: return "sndFileStartXFTP" - case .sndFileProgressXFTP: return "sndFileProgressXFTP" - case .sndFileRedirectStartXFTP: return "sndFileRedirectStartXFTP" - case .sndFileRcvCancelled: return "sndFileRcvCancelled" - case .sndFileCompleteXFTP: return "sndFileCompleteXFTP" - case .sndStandaloneFileComplete: return "sndStandaloneFileComplete" - case .sndFileCancelledXFTP: return "sndFileCancelledXFTP" - case .sndFileError: return "sndFileError" - case .sndFileWarning: return "sndFileWarning" - case .callInvitation: return "callInvitation" - case .callOffer: return "callOffer" - case .callAnswer: return "callAnswer" - case .callExtraInfo: return "callExtraInfo" - case .callEnded: return "callEnded" - case .callInvitations: return "callInvitations" - case .ntfTokenStatus: return "ntfTokenStatus" - case .ntfToken: return "ntfToken" - case .ntfMessages: return "ntfMessages" - case .ntfMessage: return "ntfMessage" - case .contactConnectionDeleted: return "contactConnectionDeleted" - case .contactDisabled: return "contactDisabled" - case .remoteCtrlList: return "remoteCtrlList" - case .remoteCtrlFound: return "remoteCtrlFound" - case .remoteCtrlConnecting: return "remoteCtrlConnecting" - case .remoteCtrlSessionCode: return "remoteCtrlSessionCode" - case .remoteCtrlConnected: return "remoteCtrlConnected" - case .remoteCtrlStopped: return "remoteCtrlStopped" - case .contactPQEnabled: return "contactPQEnabled" - case .versionInfo: return "versionInfo" - case .cmdOk: return "cmdOk" - case .agentSubsTotal: return "agentSubsTotal" - case .agentServersSummary: return "agentServersSummary" - case .agentSubsSummary: return "agentSubsSummary" - case .chatCmdError: return "chatCmdError" - case .chatError: return "chatError" - case .archiveExported: return "archiveExported" - case .archiveImported: return "archiveImported" - case .appSettings: return "appSettings" + switch self { + case let .result(r): r.responseType + case let .error(e): "error \(e.errorType)" + case let .invalid(type, _): "* \(type)" + } + } + + public var unexpected: ChatError { + switch self { + case let .result(r): .unexpectedResult(type: r.responseType) + case let .error(e): e + case let .invalid(type, _): .unexpectedResult(type: "* \(type)") + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if container.contains(.result) { + let result = try container.decode(R.self, forKey: .result) + self = .result(result) + } else { + let error = try container.decode(ChatError.self, forKey: .error) + self = .error(error) + } + } + + private enum CodingKeys: String, CodingKey { + case result, error + } +} + +public protocol ChatAPIResult: Decodable { + var responseType: String { get } + var details: String { get } + static func fallbackResult(_ type: String, _ json: NSDictionary) -> Self? +} + +extension ChatAPIResult { + public var noDetails: String { "\(self.responseType): no details" } + + @inline(__always) + public static func fallbackResult(_ type: String, _ json: NSDictionary) -> Self? { + nil + } + + @inline(__always) + public var unexpected: ChatError { + .unexpectedResult(type: self.responseType) + } +} + +public func decodeAPIResult(_ d: Data) -> APIResult { +// print("decodeAPIResult \(String(describing: R.self))") + do { +// return try withStackSizeLimit { try jsonDecoder.decode(APIResult.self, from: d) } + return try jsonDecoder.decode(APIResult.self, from: d) + } catch {} + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let (_, jErr) = getOWSF(j, "error") { + return APIResult.error(.invalidJSON(json: errorJson(jErr))) as APIResult + } else if let (type, jRes) = getOWSF(j, "result") { + return if let r = R.fallbackResult(type, jRes) { + APIResult.result(r) + } else { + APIResult.invalid(type: type, json: dataPrefix(d)) } } } + return APIResult.invalid(type: "invalid", json: dataPrefix(d)) +} - public var details: String { - get { - switch self { - case let .response(_, json): return json - case let .activeUser(user): return String(describing: user) - case let .usersList(users): return String(describing: users) - case .chatStarted: return noDetails - case .chatRunning: return noDetails - case .chatStopped: return noDetails - case .chatSuspended: return noDetails - case let .apiChats(u, chats): return withUser(u, String(describing: chats)) - case let .apiChat(u, chat): return withUser(u, String(describing: chat)) - case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") - case let .userProtoServers(u, servers): return withUser(u, "servers: \(String(describing: servers))") - case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") - case let .chatItemTTL(u, chatItemTTL): return withUser(u, String(describing: chatItemTTL)) - case let .networkConfig(networkConfig): return String(describing: networkConfig) - case let .contactInfo(u, contact, connectionStats_, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats_: \(String(describing: connectionStats_))\ncustomUserProfile: \(String(describing: customUserProfile))") - case let .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") - case let .queueInfo(u, rcvMsgInfo, queueInfo): - let msgInfo = if let info = rcvMsgInfo { encodeJSON(info) } else { "none" } - return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") - case let .contactSwitchStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") - case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") - case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") - case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") - case let .contactVerificationReset(u, contact): return withUser(u, "contact: \(String(describing: contact))") - case let .groupMemberVerificationReset(u, groupInfo, member): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))") - case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") - case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") - case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") - case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)") - case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan)) - case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) - case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) - case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) - case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) - case let .contactRequestAlreadyAccepted(u, contact): return withUser(u, String(describing: contact)) - case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) - case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) - case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) - case .userProfileNoChange: return noDetails - case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) - case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) - case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) - case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") - case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkCreated(u, connReq): return withUser(u, connReq) - case .userContactLinkDeleted: return noDetails - case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) - case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) - case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) - case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) - case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact)) - case .contactRequestRejected: return noDetails - case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact)) - case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)") - case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" - case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) - case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors)) - case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case .userContactLinkSubscribed: return noDetails - case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") - case let .chatItemsDeleted(u, items, byUser): - let itemsString = items.map { item in - "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") - return withUser(u, itemsString + "\nbyUser: \(byUser)") - case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) - case let .groupCreated(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .sentGroupInvitation(u, groupInfo, contact, member): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmember: \(member)") - case let .userAcceptedGroupSent(u, groupInfo, hostContact): return withUser(u, "groupInfo: \(groupInfo)\nhostContact: \(String(describing: hostContact))") - case let .groupLinkConnecting(u, groupInfo, hostMember): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))") - case let .userDeletedMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .groupMembers(u, group): return withUser(u, String(describing: group)) - case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") - case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") - case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") - case let .memberRoleUser(u, groupInfo, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") - case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") - case let .memberBlockedForAllUser(u, groupInfo, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nblocked: \(blocked)") - case let .deletedMemberUser(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .deletedMember(u, groupInfo, byMember, deletedMember): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)") - case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") - case let .groupInvitation(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") - case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .groupLinkCreated(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)") - case let .groupLink(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)") - case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) - case .rcvFileAcceptedSndCancelled: return noDetails - case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) - case .rcvStandaloneFileCreated: return noDetails - case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") - case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) - case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileError(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") - case let .rcvFileWarning(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") - case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) - case .sndStandaloneFileCreated: return noDetails - case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") - case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) - case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) - case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .callInvitation(inv): return String(describing: inv) - case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") - case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") - case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))") - case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") - case let .callInvitations(invs): return String(describing: invs) - case let .ntfTokenStatus(status): return String(describing: status) - case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" - case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))") - case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") - case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) - case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) - case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls) - case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)" - case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)" - case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)" - case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) - case let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" - case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") - case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))" - case .cmdOk: return noDetails - case let .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") - case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) - case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) - case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) - case let .archiveExported(archiveErrors): return String(describing: archiveErrors) - case let .archiveImported(archiveErrors): return String(describing: archiveErrors) - case let .appSettings(appSettings): return String(describing: appSettings) +// Default stack size for the main thread is 1mb, for secondary threads - 512 kb. +// This function can be used to test what size is used (or to increase available stack size). +// Stack size must be a multiple of system page size (16kb). +//private let stackSizeLimit: Int = 256 * 1024 +// +//private func withStackSizeLimit(_ f: @escaping () throws -> T) throws -> T { +// let semaphore = DispatchSemaphore(value: 0) +// var result: Result? +// let thread = Thread { +// do { +// result = .success(try f()) +// } catch { +// result = .failure(error) +// } +// semaphore.signal() +// } +// +// thread.stackSize = stackSizeLimit +// thread.qualityOfService = Thread.current.qualityOfService +// thread.start() +// +// semaphore.wait() +// +// switch result! { +// case let .success(r): return r +// case let .failure(e): throw e +// } +//} + +public func parseApiChats(_ jResp: NSDictionary) -> (user: UserRef, chats: [ChatData])? { + if let jApiChats = jResp["apiChats"] as? NSDictionary, + let user: UserRef = try? decodeObject(jApiChats["user"] as Any), + let jChats = jApiChats["chats"] as? NSArray { + let chats = jChats.map { jChat in + if let chatData = try? parseChatData(jChat) { + return chatData.0 } + return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted)) } - } - - private var noDetails: String { get { "\(responseType): no details" } } - - private func withUser(_ u: (any UserLike)?, _ s: String) -> String { - if let id = u?.userId { - return "userId: \(id)\n\(s)" - } - return s + return (user, chats) + } else { + return nil } } -public func chatError(_ chatResponse: ChatResponse) -> ChatErrorType? { - switch chatResponse { - case let .chatCmdError(_, .error(error)): return error - case let .chatError(_, .error(error)): return error - default: return nil +public func withUser(_ u: (any UserLike)?, _ s: String) -> String { + if let id = u?.userId { + return "userId: \(id)\n\(s)" + } + return s +} + +public struct CreatedConnLink: Decodable, Hashable { + public var connFullLink: String + public var connShortLink: String? + + public init(connFullLink: String, connShortLink: String?) { + self.connFullLink = connFullLink + self.connShortLink = connShortLink + } + + public func simplexChatUri(short: Bool = true) -> String { + short ? (connShortLink ?? simplexChatLink(connFullLink)) : simplexChatLink(connFullLink) } } -public enum ChatDeleteMode: Codable { - case full(notify: Bool) - case entity(notify: Bool) - case messages - - var cmdString: String { - switch self { - case let .full(notify): "full notify=\(onOff(notify))" - case let .entity(notify): "entity notify=\(onOff(notify))" - case .messages: "messages" - } - } - - public var isEntity: Bool { - switch self { - case .entity: return true - default: return false - } - } +public func simplexChatLink(_ uri: String) -> String { + uri.starts(with: "simplex:/") + ? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") + : uri } -public enum ConnectionPlan: Decodable, Hashable { - case invitationLink(invitationLinkPlan: InvitationLinkPlan) - case contactAddress(contactAddressPlan: ContactAddressPlan) - case groupLink(groupLinkPlan: GroupLinkPlan) -} - -public enum InvitationLinkPlan: Decodable, Hashable { - case ok - case ownLink - case connecting(contact_: Contact?) - case known(contact: Contact) -} - -public enum ContactAddressPlan: Decodable, Hashable { - case ok - case ownLink - case connectingConfirmReconnect - case connectingProhibit(contact: Contact) - case known(contact: Contact) - case contactViaAddress(contact: Contact) -} - -public enum GroupLinkPlan: Decodable, Hashable { - case ok - case ownLink(groupInfo: GroupInfo) - case connectingConfirmReconnect - case connectingProhibit(groupInfo_: GroupInfo?) - case known(groupInfo: GroupInfo) -} - -struct NewUser: Encodable, Hashable { - var profile: Profile? - var pastTimestamp: Bool -} - -public enum ChatPagination: Hashable { - case last(count: Int) - case after(chatItemId: Int64, count: Int) - case before(chatItemId: Int64, count: Int) - - var cmdString: String { - switch self { - case let .last(count): return "count=\(count)" - case let .after(chatItemId, count): return "after=\(chatItemId) count=\(count)" - case let .before(chatItemId, count): return "before=\(chatItemId) count=\(count)" - } - } -} - -struct ComposedMessage: Encodable { - var fileSource: CryptoFile? +public struct ComposedMessage: Encodable { + public var fileSource: CryptoFile? var quotedItemId: Int64? - var msgContent: MsgContent -} + public var msgContent: MsgContent + public var mentions: [String: Int64] -public struct ArchiveConfig: Encodable { - var archivePath: String - var disableCompression: Bool? - - public init(archivePath: String, disableCompression: Bool? = nil) { - self.archivePath = archivePath - self.disableCompression = disableCompression + public init(fileSource: CryptoFile? = nil, quotedItemId: Int64? = nil, msgContent: MsgContent, mentions: [String: Int64] = [:]) { + self.fileSource = fileSource + self.quotedItemId = quotedItemId + self.msgContent = msgContent + self.mentions = mentions } } -public struct DBEncryptionConfig: Codable { - public init(currentKey: String, newKey: String) { - self.currentKey = currentKey - self.newKey = newKey - } - - public var currentKey: String - public var newKey: String -} - -struct SMPServersConfig: Encodable { - var smpServers: [ServerCfg] -} - public enum ServerProtocol: String, Decodable { case smp case xftp } -public struct ProtoServersConfig: Codable { - public var servers: [ServerCfg] -} - -public struct UserProtoServers: Decodable { - public var serverProtocol: ServerProtocol - public var protoServers: [ServerCfg] - public var presetServers: [ServerCfg] -} - -public struct ServerCfg: Identifiable, Equatable, Codable, Hashable { - public var server: String - public var preset: Bool - public var tested: Bool? - public var enabled: Bool - var createdAt = Date() -// public var sendEnabled: Bool // can we potentially want to prevent sending on the servers we use to receive? -// Even if we don't see the use case, it's probably better to allow it in the model -// In any case, "trusted/known" servers are out of scope of this change - - public init(server: String, preset: Bool, tested: Bool?, enabled: Bool) { - self.server = server - self.preset = preset - self.tested = tested - self.enabled = enabled - } - - public static func == (l: ServerCfg, r: ServerCfg) -> Bool { - l.server == r.server && l.preset == r.preset && l.tested == r.tested && l.enabled == r.enabled - } - - public var id: String { "\(server) \(createdAt)" } - - public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: false) - - public var isEmpty: Bool { - server.trimmingCharacters(in: .whitespaces) == "" - } - - public struct SampleData { - public var preset: ServerCfg - public var custom: ServerCfg - public var untested: ServerCfg - } - - public static var sampleData = SampleData( - preset: ServerCfg( - server: "smp://abcd@smp8.simplex.im", - preset: true, - tested: true, - enabled: true - ), - custom: ServerCfg( - server: "smp://abcd@smp9.simplex.im", - preset: false, - tested: false, - enabled: false - ), - untested: ServerCfg( - server: "smp://abcd@smp10.simplex.im", - preset: false, - tested: nil, - enabled: true - ) - ) - - enum CodingKeys: CodingKey { - case server - case preset - case tested - case enabled - } -} - -public enum ProtocolTestStep: String, Decodable, Equatable { - case connect - case disconnect - case createQueue - case secureQueue - case deleteQueue - case createFile - case uploadFile - case downloadFile - case compareFile - case deleteFile - - var text: String { - switch self { - case .connect: return NSLocalizedString("Connect", comment: "server test step") - case .disconnect: return NSLocalizedString("Disconnect", comment: "server test step") - case .createQueue: return NSLocalizedString("Create queue", comment: "server test step") - case .secureQueue: return NSLocalizedString("Secure queue", comment: "server test step") - case .deleteQueue: return NSLocalizedString("Delete queue", comment: "server test step") - case .createFile: return NSLocalizedString("Create file", comment: "server test step") - case .uploadFile: return NSLocalizedString("Upload file", comment: "server test step") - case .downloadFile: return NSLocalizedString("Download file", comment: "server test step") - case .compareFile: return NSLocalizedString("Compare file", comment: "server test step") - case .deleteFile: return NSLocalizedString("Delete file", comment: "server test step") - } - } -} - -public struct ProtocolTestFailure: Decodable, Error, Equatable { - public var testStep: ProtocolTestStep - public var testError: AgentErrorType - - public static func == (l: ProtocolTestFailure, r: ProtocolTestFailure) -> Bool { - l.testStep == r.testStep - } - - public var localizedDescription: String { - let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text) - switch testError { - case .SMP(_, .AUTH): - return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error") - case .XFTP(.AUTH): - return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error") - case .BROKER(_, .NETWORK): - return err + " " + NSLocalizedString("Possibly, certificate fingerprint in server address is incorrect", comment: "server test error") - default: - return err - } - } -} - public struct ServerAddress: Decodable { public var serverProtocol: ServerProtocol public var hostnames: [String] @@ -1315,14 +232,15 @@ public struct ServerAddress: Decodable { ) } -public struct NetCfg: Codable, Equatable, Hashable { +public struct NetCfg: Codable, Equatable { public var socksProxy: String? = nil var socksMode: SocksMode = .always public var hostMode: HostMode = .publicHost public var requiredHostMode = true public var sessionMode = TransportSessionMode.user - public var smpProxyMode: SMPProxyMode = .unknown + public var smpProxyMode: SMPProxyMode = .always public var smpProxyFallback: SMPProxyFallback = .allowProtected + public var smpWebPortServers: SMPWebPortServers = .preset public var tcpConnectTimeout: Int // microseconds public var tcpTimeout: Int // microseconds public var tcpTimeoutPerKb: Int // microseconds @@ -1347,7 +265,7 @@ public struct NetCfg: Codable, Equatable, Hashable { rcvConcurrency: 8, smpPingInterval: 1200_000_000 ) - + public var withProxyTimeouts: NetCfg { var cfg = self cfg.tcpConnectTimeout = NetCfg.proxyDefaults.tcpConnectTimeout @@ -1357,7 +275,7 @@ public struct NetCfg: Codable, Equatable, Hashable { cfg.smpPingInterval = NetCfg.proxyDefaults.smpPingInterval return cfg } - + public var hasProxyTimeouts: Bool { tcpConnectTimeout == NetCfg.proxyDefaults.tcpConnectTimeout && tcpTimeout == NetCfg.proxyDefaults.tcpTimeout && @@ -1369,18 +287,18 @@ public struct NetCfg: Codable, Equatable, Hashable { public var enableKeepAlive: Bool { tcpKeepAlive != nil } } -public enum HostMode: String, Codable, Hashable { +public enum HostMode: String, Codable { case onionViaSocks case onionHost = "onion" case publicHost = "public" } -public enum SocksMode: String, Codable, Hashable { +public enum SocksMode: String, Codable { case always = "always" case onion = "onion" } -public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { +public enum SMPProxyMode: String, Codable, SelectableItem { case always = "always" case unknown = "unknown" case unprotected = "unprotected" @@ -1400,7 +318,7 @@ public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never] } -public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { +public enum SMPProxyFallback: String, Codable, SelectableItem { case allow = "allow" case allowProtected = "allowProtected" case prohibit = "prohibit" @@ -1418,7 +336,21 @@ public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] } -public enum OnionHosts: String, Identifiable, Hashable { +public enum SMPWebPortServers: String, Codable, CaseIterable { + case all = "all" + case preset = "preset" + case off = "off" + + public var text: LocalizedStringKey { + switch self { + case .all: "All servers" + case .preset: "Preset servers" + case .off: "Off" + } + } +} + +public enum OnionHosts: String, Identifiable { case no case prefer case require @@ -1452,23 +384,27 @@ public enum OnionHosts: String, Identifiable, Hashable { public static let values: [OnionHosts] = [.no, .prefer, .require] } -public enum TransportSessionMode: String, Codable, Identifiable, Hashable { +public enum TransportSessionMode: String, Codable, Identifiable { case user + case session + case server case entity public var text: LocalizedStringKey { switch self { - case .user: return "User profile" + case .user: return "Chat profile" + case .session: return "App session" + case .server: return "Server" case .entity: return "Connection" } } public var id: TransportSessionMode { self } - public static let values: [TransportSessionMode] = [.user, .entity] + public static let values: [TransportSessionMode] = [.user, .session, .server, .entity] } -public struct KeepAliveOpts: Codable, Equatable, Hashable { +public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds public var keepCnt: Int // times @@ -1476,47 +412,61 @@ public struct KeepAliveOpts: Codable, Equatable, Hashable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } -public enum NetworkStatus: Decodable, Equatable, Hashable { - case unknown - case connected - case disconnected - case error(connectionError: String) +public struct NetworkProxy: Equatable, Codable { + public var host: String = "" + public var port: Int = 0 + public var auth: NetworkProxyAuth = .username + public var username: String = "" + public var password: String = "" - public var statusString: LocalizedStringKey { - get { - switch self { - case .connected: return "connected" - case .error: return "error" - default: return "connecting" - } - } + public static var def: NetworkProxy { + NetworkProxy() } - public var statusExplanation: LocalizedStringKey { - get { - switch self { - case .connected: return "You are connected to the server used to receive messages from this contact." - case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))." - default: return "Trying to connect to the server used to receive messages from this contact." - } + public var valid: Bool { + let hostOk = switch NWEndpoint.Host(host) { + case .ipv4: true + case .ipv6: true + default: false } + return hostOk && + port > 0 && port <= 65535 && + NetworkProxy.validCredential(username) && NetworkProxy.validCredential(password) } - public var imageName: String { - get { - switch self { - case .unknown: return "circle.dotted" - case .connected: return "circle.fill" - case .disconnected: return "ellipsis.circle.fill" - case .error: return "exclamationmark.circle.fill" + public static func validCredential(_ s: String) -> Bool { + !s.contains(":") && !s.contains("@") + } + + public func toProxyString() -> String? { + if !valid { return nil } + var res = "" + switch auth { + case .username: + let usernameTrimmed = username.trimmingCharacters(in: .whitespaces) + let passwordTrimmed = password.trimmingCharacters(in: .whitespaces) + if usernameTrimmed != "" || passwordTrimmed != "" { + res += usernameTrimmed + ":" + passwordTrimmed + "@" + } else { + res += "@" + } + case .isolate: () + } + if host != "" { + if host.contains(":") { + res += "[\(host.trimmingCharacters(in: [" ", "[", "]"]))]" + } else { + res += host.trimmingCharacters(in: .whitespaces) } } + res += ":\(port)" + return res } } -public struct ConnNetworkStatus: Decodable, Hashable { - public var agentConnId: String - public var networkStatus: NetworkStatus +public enum NetworkProxyAuth: String, Codable { + case username + case isolate } public struct ChatSettings: Codable, Hashable { @@ -1533,19 +483,54 @@ public struct ChatSettings: Codable, Hashable { public static let defaults: ChatSettings = ChatSettings(enableNtfs: .all, sendRcpts: nil, favorite: false) } +public struct NavigationInfo: Decodable { + public var afterUnread: Int = 0 + public var afterTotal: Int = 0 + + public init(afterUnread: Int = 0, afterTotal: Int = 0) { + self.afterUnread = afterUnread + self.afterTotal = afterTotal + } +} + public enum MsgFilter: String, Codable, Hashable { case none case all case mentions -} - -public struct UserMsgReceiptSettings: Codable, Hashable { - public var enable: Bool - public var clearOverrides: Bool - - public init(enable: Bool, clearOverrides: Bool) { - self.enable = enable - self.clearOverrides = clearOverrides + + public func nextMode(mentions: Bool) -> MsgFilter { + switch self { + case .all: mentions ? .mentions : .none + case .mentions: .none + case .none: .all + } + } + + public func text(mentions: Bool) -> String { + switch self { + case .all: NSLocalizedString("Unmute", comment: "notification label action") + case .mentions: NSLocalizedString("Mute", comment: "notification label action") + case .none: + mentions + ? NSLocalizedString("Mute all", comment: "notification label action") + : NSLocalizedString("Mute", comment: "notification label action") + } + } + + public var icon: String { + return switch self { + case .all: "speaker.wave.2" + case .mentions: "speaker.badge.exclamationmark" + case .none: "speaker.slash" + } + } + + public var iconFilled: String { + return switch self { + case .all: "speaker.wave.2.fill" + case .mentions: "speaker.badge.exclamationmark.fill" + case .none: "speaker.slash.fill" + } } } @@ -1563,6 +548,10 @@ public struct ConnectionStats: Decodable, Hashable { public var ratchetSyncSendProhibited: Bool { [.required, .started, .agreed].contains(ratchetSyncState) } + + public var ratchetSyncInProgress: Bool { + [.started, .agreed].contains(ratchetSyncState) + } } public struct RcvQueueInfo: Codable, Hashable { @@ -1588,7 +577,7 @@ public enum SndSwitchStatus: String, Codable, Hashable { case sendingQTEST = "sending_qtest" } -public enum QueueDirection: String, Decodable, Hashable { +public enum QueueDirection: String, Decodable { case rcv case snd } @@ -1612,94 +601,12 @@ public enum RatchetSyncState: String, Decodable { case agreed } -public struct UserContactLink: Decodable, Hashable { - public var connReqContact: String - public var autoAccept: AutoAccept? - - public init(connReqContact: String, autoAccept: AutoAccept? = nil) { - self.connReqContact = connReqContact - self.autoAccept = autoAccept - } - - var responseDetails: String { - "connReqContact: \(connReqContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" - } -} - -public struct AutoAccept: Codable, Hashable { - public var acceptIncognito: Bool - public var autoReply: MsgContent? - - public init(acceptIncognito: Bool, autoReply: MsgContent? = nil) { - self.acceptIncognito = acceptIncognito - self.autoReply = autoReply - } - - static func cmdString(_ autoAccept: AutoAccept?) -> String { - guard let autoAccept = autoAccept else { return "off" } - let s = "on" + (autoAccept.acceptIncognito ? " incognito=on" : "") - guard let msg = autoAccept.autoReply else { return s } - return s + " " + msg.cmdString - } -} - -public protocol SelectableItem: Hashable, Identifiable { +public protocol SelectableItem: Identifiable, Equatable { var label: LocalizedStringKey { get } static var values: [Self] { get } } -public struct DeviceToken: Decodable, Hashable { - var pushProvider: PushProvider - var token: String - - public init(pushProvider: PushProvider, token: String) { - self.pushProvider = pushProvider - self.token = token - } - - public var cmdString: String { - "\(pushProvider) \(token)" - } -} - -public enum PushEnvironment: String, Hashable { - case development - case production -} - -public enum PushProvider: String, Decodable, Hashable { - case apns_dev - case apns_prod - - public init(env: PushEnvironment) { - switch env { - case .development: self = .apns_dev - case .production: self = .apns_prod - } - } -} - -// This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, -// and .local for periodic background checks -public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { - case off = "OFF" - case periodic = "PERIODIC" - case instant = "INSTANT" - - public var label: LocalizedStringKey { - switch self { - case .off: "Local" - case .periodic: "Periodically" - case .instant: "Instantly" - } - } - - public var id: String { self.rawValue } - - public static var values: [NotificationsMode] = [.instant, .periodic, .off] -} - -public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { +public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact case message @@ -1717,47 +624,6 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] } -public struct RemoteCtrlInfo: Decodable, Hashable { - public var remoteCtrlId: Int64 - public var ctrlDeviceName: String - public var sessionState: RemoteCtrlSessionState? - - public var deviceViewName: String { - ctrlDeviceName == "" ? "\(remoteCtrlId)" : ctrlDeviceName - } -} - -public enum RemoteCtrlSessionState: Decodable, Hashable { - case starting - case searching - case connecting - case pendingConfirmation(sessionCode: String) - case connected(sessionCode: String) -} - -public enum RemoteCtrlStopReason: Decodable { - case discoveryFailed(chatError: ChatError) - case connectionFailed(chatError: ChatError) - case setupFailed(chatError: ChatError) - case disconnected -} - -public struct CtrlAppInfo: Decodable, Hashable { - public var appVersionRange: AppVersionRange - public var deviceName: String -} - -public struct AppVersionRange: Decodable, Hashable { - public var minVersion: String - public var maxVersion: String -} - -public struct CoreVersionInfo: Decodable, Hashable { - public var version: String - public var simplexmqVersion: String - public var simplexmqCommit: String -} - public func decodeJSON(_ json: String) -> T? { if let data = json.data(using: .utf8) { return try? jsonDecoder.decode(T.self, from: data) @@ -1774,13 +640,26 @@ private func encodeCJSON(_ value: T) -> [CChar] { encodeJSON(value).cString(using: .utf8)! } -public enum ChatError: Decodable, Hashable { +public enum ChatError: Decodable, Hashable, Error { case error(errorType: ChatErrorType) case errorAgent(agentError: AgentErrorType) case errorStore(storeError: StoreError) case errorDatabase(databaseError: DatabaseError) case errorRemoteCtrl(remoteCtrlError: RemoteCtrlError) - case invalidJSON(json: String) + case invalidJSON(json: Data?) // additional case used to pass errors that failed to parse + case unexpectedResult(type: String) // additional case used to pass unexpected responses + + public var errorType: String { + switch self { + case .error: "chat" + case .errorAgent: "agent" + case .errorStore: "store" + case .errorDatabase: "database" + case .errorRemoteCtrl: "remoteCtrl" + case .invalidJSON: "invalid" + case let .unexpectedResult(type): "! \(type)" + } + } } public enum ChatErrorType: Decodable, Hashable { @@ -1803,8 +682,8 @@ public enum ChatErrorType: Decodable, Hashable { case chatNotStarted case chatNotStopped case chatStoreChanged - case connectionPlan(connectionPlan: ConnectionPlan) case invalidConnReq + case unsupportedConnReq case invalidChatMessage(connection: Connection, message: String) case contactNotReady(contact: Contact) case contactNotActive(contact: Contact) @@ -1842,7 +721,6 @@ public enum ChatErrorType: Decodable, Hashable { case inlineFileProhibited(fileId: Int64) case invalidQuote case invalidForward - case forwardNoFile case invalidChatItemUpdate case invalidChatItemDelete case hasCurrentCall @@ -1857,6 +735,7 @@ public enum ChatErrorType: Decodable, Hashable { case agentCommandError(message: String) case invalidFileDescription(message: String) case connectionIncognitoChangeProhibited + case connectionUserChangeProhibited case peerChatVRangeIncompatible case internalError(message: String) case exception(message: String) @@ -1920,6 +799,7 @@ public enum StoreError: Decodable, Hashable { case hostMemberIdNotFound(groupId: Int64) case contactNotFoundByFileId(fileId: Int64) case noGroupSndStatus(itemId: Int64, groupMemberId: Int64) + case dBException(message: String) } public enum DatabaseError: Decodable, Hashable { @@ -1936,7 +816,7 @@ public enum SQLiteError: Decodable, Hashable { } public enum AgentErrorType: Decodable, Hashable { - case CMD(cmdErr: CommandErrorType) + case CMD(cmdErr: CommandErrorType, errContext: String) case CONN(connErr: ConnectionErrorType) case SMP(serverAddress: String, smpErr: ProtocolErrorType) case NTF(ntfErr: ProtocolErrorType) @@ -1981,8 +861,10 @@ public enum ProtocolErrorType: Decodable, Hashable { case CMD(cmdErr: ProtocolCommandError) indirect case PROXY(proxyErr: ProxyError) case AUTH + case BLOCKED(blockInfo: BlockingInfo) case CRYPTO case QUOTA + case STORE(storeErr: String) case NO_MSG case LARGE_MSG case EXPIRED @@ -1996,11 +878,28 @@ public enum ProxyError: Decodable, Hashable { case NO_SESSION } +public struct BlockingInfo: Decodable, Equatable, Hashable { + public var reason: BlockingReason +} + +public enum BlockingReason: String, Decodable { + case spam + case content + + public var text: String { + switch self { + case .spam: NSLocalizedString("Spam", comment: "blocking reason") + case .content: NSLocalizedString("Content violates conditions of use", comment: "blocking reason") + } + } +} + public enum XFTPErrorType: Decodable, Hashable { case BLOCK case SESSION case CMD(cmdErr: ProtocolCommandError) case AUTH + case BLOCKED(blockInfo: BlockingInfo) case SIZE case QUOTA case DIGEST @@ -2090,398 +989,14 @@ public enum RemoteCtrlError: Decodable, Hashable { case protocolError } -public struct MigrationFileLinkData: Codable, Hashable { - let networkConfig: NetworkConfig? - - public init(networkConfig: NetworkConfig) { - self.networkConfig = networkConfig - } - - public struct NetworkConfig: Codable, Hashable { - let socksProxy: String? - let hostMode: HostMode? - let requiredHostMode: Bool? - - public init(socksProxy: String?, hostMode: HostMode?, requiredHostMode: Bool?) { - self.socksProxy = socksProxy - self.hostMode = hostMode - self.requiredHostMode = requiredHostMode - } - - public func transformToPlatformSupported() -> NetworkConfig { - return if let hostMode, let requiredHostMode { - NetworkConfig( - socksProxy: nil, - hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, - requiredHostMode: requiredHostMode - ) - } else { self } - } - } - - public func addToLink(link: String) -> String { - "\(link)&data=\(encodeJSON(self).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)" - } - - public static func readFromLink(link: String) -> MigrationFileLinkData? { -// standaloneFileInfo(link) - nil - } -} - -public struct AppSettings: Codable, Equatable, Hashable { - public var networkConfig: NetCfg? = nil - public var privacyEncryptLocalFiles: Bool? = nil - public var privacyAskToApproveRelays: Bool? = nil - public var privacyAcceptImages: Bool? = nil - public var privacyLinkPreviews: Bool? = nil - public var privacyShowChatPreviews: Bool? = nil - public var privacySaveLastDraft: Bool? = nil - public var privacyProtectScreen: Bool? = nil - public var privacyMediaBlurRadius: Int? = nil - public var notificationMode: AppSettingsNotificationMode? = nil - public var notificationPreviewMode: NotificationPreviewMode? = nil - public var webrtcPolicyRelay: Bool? = nil - public var webrtcICEServers: [String]? = nil - public var confirmRemoteSessions: Bool? = nil - public var connectRemoteViaMulticast: Bool? = nil - public var connectRemoteViaMulticastAuto: Bool? = nil - public var developerTools: Bool? = nil - public var confirmDBUpgrades: Bool? = nil - public var androidCallOnLockScreen: AppSettingsLockScreenCalls? = nil - public var iosCallKitEnabled: Bool? = nil - public var iosCallKitCallsInRecents: Bool? = nil - public var uiProfileImageCornerRadius: Double? = nil - public var uiColorScheme: String? = nil - public var uiDarkColorScheme: String? = nil - public var uiCurrentThemeIds: [String: String]? = nil - public var uiThemes: [ThemeOverrides]? = nil - public var oneHandUI: Bool? = nil - - public func prepareForExport() -> AppSettings { - var empty = AppSettings() - let def = AppSettings.defaults - if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } - if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } - if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } - if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } - if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } - if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } - if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } - if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } - if privacyMediaBlurRadius != def.privacyMediaBlurRadius { empty.privacyMediaBlurRadius = privacyMediaBlurRadius } - if notificationMode != def.notificationMode { empty.notificationMode = notificationMode } - if notificationPreviewMode != def.notificationPreviewMode { empty.notificationPreviewMode = notificationPreviewMode } - if webrtcPolicyRelay != def.webrtcPolicyRelay { empty.webrtcPolicyRelay = webrtcPolicyRelay } - if webrtcICEServers != def.webrtcICEServers { empty.webrtcICEServers = webrtcICEServers } - if confirmRemoteSessions != def.confirmRemoteSessions { empty.confirmRemoteSessions = confirmRemoteSessions } - if connectRemoteViaMulticast != def.connectRemoteViaMulticast {empty.connectRemoteViaMulticast = connectRemoteViaMulticast } - if connectRemoteViaMulticastAuto != def.connectRemoteViaMulticastAuto { empty.connectRemoteViaMulticastAuto = connectRemoteViaMulticastAuto } - if developerTools != def.developerTools { empty.developerTools = developerTools } - if confirmDBUpgrades != def.confirmDBUpgrades { empty.confirmDBUpgrades = confirmDBUpgrades } - if androidCallOnLockScreen != def.androidCallOnLockScreen { empty.androidCallOnLockScreen = androidCallOnLockScreen } - if iosCallKitEnabled != def.iosCallKitEnabled { empty.iosCallKitEnabled = iosCallKitEnabled } - if iosCallKitCallsInRecents != def.iosCallKitCallsInRecents { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } - if uiProfileImageCornerRadius != def.uiProfileImageCornerRadius { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius } - if uiColorScheme != def.uiColorScheme { empty.uiColorScheme = uiColorScheme } - if uiDarkColorScheme != def.uiDarkColorScheme { empty.uiDarkColorScheme = uiDarkColorScheme } - if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds } - if uiThemes != def.uiThemes { empty.uiThemes = uiThemes } - if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI } - return empty - } - - public static var defaults: AppSettings { - AppSettings ( - networkConfig: NetCfg.defaults, - privacyEncryptLocalFiles: true, - privacyAskToApproveRelays: true, - privacyAcceptImages: true, - privacyLinkPreviews: true, - privacyShowChatPreviews: true, - privacySaveLastDraft: true, - privacyProtectScreen: false, - privacyMediaBlurRadius: 0, - notificationMode: AppSettingsNotificationMode.instant, - notificationPreviewMode: NotificationPreviewMode.message, - webrtcPolicyRelay: true, - webrtcICEServers: [], - confirmRemoteSessions: false, - connectRemoteViaMulticast: true, - connectRemoteViaMulticastAuto: true, - developerTools: false, - confirmDBUpgrades: false, - androidCallOnLockScreen: AppSettingsLockScreenCalls.show, - iosCallKitEnabled: true, - iosCallKitCallsInRecents: false, - uiProfileImageCornerRadius: 22.5, - uiColorScheme: DefaultTheme.SYSTEM_THEME_NAME, - uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, - uiCurrentThemeIds: nil as [String: String]?, - uiThemes: nil as [ThemeOverrides]?, - oneHandUI: false - ) - } -} - -public enum AppSettingsNotificationMode: String, Codable, Hashable { - case off - case periodic - case instant - - public func toNotificationsMode() -> NotificationsMode { - switch self { - case .instant: .instant - case .periodic: .periodic - case .off: .off - } - } - - public static func from(_ mode: NotificationsMode) -> AppSettingsNotificationMode { - switch mode { - case .instant: .instant - case .periodic: .periodic - case .off: .off - } - } -} - -//public enum NotificationPreviewMode: Codable { -// case hidden -// case contact -// case message -//} - -public enum AppSettingsLockScreenCalls: String, Codable, Hashable { - case disable - case show - case accept -} - -public struct UserNetworkInfo: Codable, Equatable, Hashable { - public let networkType: UserNetworkType - public let online: Bool - - public init(networkType: UserNetworkType, online: Bool) { - self.networkType = networkType - self.online = online - } -} - -public enum UserNetworkType: String, Codable, Hashable { - case none - case cellular - case wifi - case ethernet - case other - - public var text: LocalizedStringKey { - switch self { - case .none: "No network connection" - case .cellular: "Cellular" - case .wifi: "WiFi" - case .ethernet: "Wired ethernet" - case .other: "Other" - } - } -} - -public struct RcvMsgInfo: Codable, Hashable { - var msgId: Int64 - var msgDeliveryId: Int64 - var msgDeliveryStatus: String - var agentMsgId: Int64 - var agentMsgMeta: String -} - -public struct QueueInfo: Codable, Hashable { - var qiSnd: Bool - var qiNtf: Bool - var qiSub: QSub? - var qiSize: Int - var qiMsg: MsgInfo? -} - -public struct QSub: Codable, Hashable { - var qSubThread: QSubThread - var qDelivered: String? -} - -public enum QSubThread: String, Codable, Hashable { - case noSub - case subPending - case subThread - case prohibitSub -} - -public struct MsgInfo: Codable, Hashable { - var msgId: String - var msgTs: Date - var msgType: MsgType -} - -public enum MsgType: String, Codable, Hashable { - case message - case quota -} - public struct AppFilePaths: Encodable { public let appFilesFolder: String public let appTempFolder: String public let appAssetsFolder: String -} - -public struct PresentedServersSummary: Codable { - public var statsStartedAt: Date - public var allUsersSMP: SMPServersSummary - public var allUsersXFTP: XFTPServersSummary - public var currentUserSMP: SMPServersSummary - public var currentUserXFTP: XFTPServersSummary -} - -public struct SMPServersSummary: Codable { - public var smpTotals: SMPTotals - public var currentlyUsedSMPServers: [SMPServerSummary] - public var previouslyUsedSMPServers: [SMPServerSummary] - public var onlyProxiedSMPServers: [SMPServerSummary] -} - -public struct SMPTotals: Codable { - public var sessions: ServerSessions - public var subs: SMPServerSubs - public var stats: AgentSMPServerStatsData -} - -public struct SMPServerSummary: Codable, Identifiable { - public var smpServer: String - public var known: Bool? - public var sessions: ServerSessions? - public var subs: SMPServerSubs? - public var stats: AgentSMPServerStatsData? - - public var id: String { smpServer } - - public var hasSubs: Bool { subs != nil } - - public var sessionsOrNew: ServerSessions { sessions ?? ServerSessions.newServerSessions } - - public var subsOrNew: SMPServerSubs { subs ?? SMPServerSubs.newSMPServerSubs } -} - -public struct ServerSessions: Codable { - public var ssConnected: Int - public var ssErrors: Int - public var ssConnecting: Int - - static public var newServerSessions = ServerSessions( - ssConnected: 0, - ssErrors: 0, - ssConnecting: 0 - ) - - public var hasSess: Bool { ssConnected > 0 } -} - -public struct SMPServerSubs: Codable { - public var ssActive: Int - public var ssPending: Int - - public init(ssActive: Int, ssPending: Int) { - self.ssActive = ssActive - self.ssPending = ssPending - } - - static public var newSMPServerSubs = SMPServerSubs( - ssActive: 0, - ssPending: 0 - ) - - public var total: Int { ssActive + ssPending } - - public var shareOfActive: Double { - guard total != 0 else { return 0.0 } - return Double(ssActive) / Double(total) + + public init(appFilesFolder: String, appTempFolder: String, appAssetsFolder: String) { + self.appFilesFolder = appFilesFolder + self.appTempFolder = appTempFolder + self.appAssetsFolder = appAssetsFolder } } - -public struct AgentSMPServerStatsData: Codable { - public var _sentDirect: Int - public var _sentViaProxy: Int - public var _sentProxied: Int - public var _sentDirectAttempts: Int - public var _sentViaProxyAttempts: Int - public var _sentProxiedAttempts: Int - public var _sentAuthErrs: Int - public var _sentQuotaErrs: Int - public var _sentExpiredErrs: Int - public var _sentOtherErrs: Int - public var _recvMsgs: Int - public var _recvDuplicates: Int - public var _recvCryptoErrs: Int - public var _recvErrs: Int - public var _ackMsgs: Int - public var _ackAttempts: Int - public var _ackNoMsgErrs: Int - public var _ackOtherErrs: Int - public var _connCreated: Int - public var _connSecured: Int - public var _connCompleted: Int - public var _connDeleted: Int - public var _connDelAttempts: Int - public var _connDelErrs: Int - public var _connSubscribed: Int - public var _connSubAttempts: Int - public var _connSubIgnored: Int - public var _connSubErrs: Int - public var _ntfKey: Int - public var _ntfKeyAttempts: Int - public var _ntfKeyDeleted: Int - public var _ntfKeyDeleteAttempts: Int -} - -public struct XFTPServersSummary: Codable { - public var xftpTotals: XFTPTotals - public var currentlyUsedXFTPServers: [XFTPServerSummary] - public var previouslyUsedXFTPServers: [XFTPServerSummary] -} - -public struct XFTPTotals: Codable { - public var sessions: ServerSessions - public var stats: AgentXFTPServerStatsData -} - -public struct XFTPServerSummary: Codable, Identifiable { - public var xftpServer: String - public var known: Bool? - public var sessions: ServerSessions? - public var stats: AgentXFTPServerStatsData? - public var rcvInProgress: Bool - public var sndInProgress: Bool - public var delInProgress: Bool - - public var id: String { xftpServer } -} - -public struct AgentXFTPServerStatsData: Codable { - public var _uploads: Int - public var _uploadsSize: Int64 - public var _uploadAttempts: Int - public var _uploadErrs: Int - public var _downloads: Int - public var _downloadsSize: Int64 - public var _downloadAttempts: Int - public var _downloadAuthErrs: Int - public var _downloadErrs: Int - public var _deletions: Int - public var _deleteAttempts: Int - public var _deleteErrs: Int -} - -public struct AgentNtfServerStatsData: Codable { - public var _ntfCreated: Int - public var _ntfCreateAttempts: Int - public var _ntfChecked: Int - public var _ntfCheckAttempts: Int - public var _ntfDeleted: Int - public var _ntfDelAttempts: Int -} diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index bd38f3568c..29ccab7357 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -35,10 +35,12 @@ public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRel // replaces DEFAULT_PROFILE_IMAGE_CORNER_RADIUS public let GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" +public let GROUP_DEFAULT_NETWORK_SOCKS_PROXY = "networkSocksProxy" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK = "networkSMPProxyFallback" +let GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS = "networkSMPWebPortServers" let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb" @@ -56,6 +58,7 @@ public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades" public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled" public let GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED = "pqExperimentalEnabled" // no longer used public let GROUP_DEFAULT_ONE_HAND_UI = "oneHandUI" +public let GROUP_DEFAULT_CHAT_BOTTOM_BAR = "chatBottomBar" public let APP_GROUP_NAME = "group.chat.simplex.app" @@ -66,9 +69,10 @@ public func registerGroupDefaults() { GROUP_DEFAULT_NTF_ENABLE_LOCAL: false, GROUP_DEFAULT_NTF_ENABLE_PERIODIC: false, GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue, - GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.user.rawValue, + GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.session.rawValue, GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE: SMPProxyMode.unknown.rawValue, GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK: SMPProxyFallback.allowProtected.rawValue, + GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS: SMPWebPortServers.preset.rawValue, GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb, @@ -84,7 +88,7 @@ public func registerGroupDefaults() { GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false, GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED: true, GROUP_DEFAULT_ALLOW_SHARE_EXTENSION: false, - GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: false, + GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: true, GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true, GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false, GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, @@ -93,7 +97,8 @@ public func registerGroupDefaults() { GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CALL_KIT_ENABLED: true, GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, - GROUP_DEFAULT_ONE_HAND_UI: true + GROUP_DEFAULT_ONE_HAND_UI: true, + GROUP_DEFAULT_CHAT_BOTTOM_BAR: true ]) } @@ -231,7 +236,7 @@ public let networkUseOnionHostsGroupDefault = EnumDefault( public let networkSessionModeGroupDefault = EnumDefault( defaults: groupDefaults, forKey: GROUP_DEFAULT_NETWORK_SESSION_MODE, - withDefault: .user + withDefault: .session ) public let networkSMPProxyModeGroupDefault = EnumDefault( @@ -246,6 +251,12 @@ public let networkSMPProxyFallbackGroupDefault = EnumDefault( withDefault: .allowProtected ) +public let networkSMPWebPortServersDefault = EnumDefault( + defaults: groupDefaults, + forKey: GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS, + withDefault: .preset +) + public let storeDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_STORE_DB_PASSPHRASE) public let initialRandomDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE) @@ -300,12 +311,14 @@ public class EnumDefault where T.RawValue == String { } public class BoolDefault: Default { + @inline(__always) public func get() -> Bool { self.defaults.bool(forKey: self.key) } } public class IntDefault: Default { + @inline(__always) public func get() -> Int { self.defaults.integer(forKey: self.key) } @@ -315,11 +328,13 @@ public class Default { var defaults: UserDefaults var key: String + @inline(__always) public init(defaults: UserDefaults = UserDefaults.standard, forKey: String) { self.defaults = defaults self.key = forKey } + @inline(__always) public func set(_ value: T) { defaults.set(value, forKey: key) defaults.synchronize() @@ -327,11 +342,13 @@ public class Default { } public func getNetCfg() -> NetCfg { + let socksProxy = groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) let onionHosts = networkUseOnionHostsGroupDefault.get() let (hostMode, requiredHostMode) = onionHosts.hostMode let sessionMode = networkSessionModeGroupDefault.get() let smpProxyMode = networkSMPProxyModeGroupDefault.get() let smpProxyFallback = networkSMPProxyFallbackGroupDefault.get() + let smpWebPortServers = networkSMPWebPortServersDefault.get() let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) @@ -349,11 +366,13 @@ public func getNetCfg() -> NetCfg { tcpKeepAlive = nil } return NetCfg( + socksProxy: socksProxy, hostMode: hostMode, requiredHostMode: requiredHostMode, sessionMode: sessionMode, smpProxyMode: smpProxyMode, smpProxyFallback: smpProxyFallback, + smpWebPortServers: smpWebPortServers, tcpConnectTimeout: tcpConnectTimeout, tcpTimeout: tcpTimeout, tcpTimeoutPerKb: tcpTimeoutPerKb, @@ -365,11 +384,14 @@ public func getNetCfg() -> NetCfg { ) } -public func setNetCfg(_ cfg: NetCfg) { +public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg)) networkSessionModeGroupDefault.set(cfg.sessionMode) networkSMPProxyModeGroupDefault.set(cfg.smpProxyMode) networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback) + let socksProxy = networkProxy?.toProxyString() + groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) + networkSMPWebPortServersDefault.set(cfg.smpWebPortServers) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) diff --git a/apps/ios/SimpleXChat/CallTypes.swift b/apps/ios/SimpleXChat/CallTypes.swift index 227a1fbda5..da1720c134 100644 --- a/apps/ios/SimpleXChat/CallTypes.swift +++ b/apps/ios/SimpleXChat/CallTypes.swift @@ -42,6 +42,7 @@ public struct RcvCallInvitation: Decodable { public var contact: Contact public var callType: CallType public var sharedKey: String? + public var callUUID: String? public var callTs: Date public var callTypeText: LocalizedStringKey { get { @@ -52,10 +53,8 @@ public struct RcvCallInvitation: Decodable { } } - public var callkitUUID: UUID? = UUID() - private enum CodingKeys: String, CodingKey { - case user, contact, callType, sharedKey, callTs + case user, contact, callType, sharedKey, callUUID, callTs } public static let sampleData = RcvCallInvitation( @@ -81,6 +80,14 @@ public enum CallMediaType: String, Codable, Equatable { case audio = "audio" } +public enum CallMediaSource: String, Codable, Equatable { + case mic = "mic" + case camera = "camera" + case screenAudio = "screenAudio" + case screenVideo = "screenVideo" + case unknown = "unknown" +} + public enum VideoCamera: String, Codable, Equatable { case user = "user" case environment = "environment" diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 1a9cf4a216..88246465e1 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -9,6 +9,14 @@ import Foundation import SwiftUI +// version to establishing direct connection with a group member (xGrpDirectInvVRange in core) +public let CREATE_MEMBER_CONTACT_VERSION = 2 + +// version to receive reports (MCReport) +public let REPORTS_VERSION = 12 + +public let contentModerationPostLink = URL(string: "https://simplex.chat/blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.html#preventing-server-abuse-without-compromising-e2e-encryption")! + public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { public var userId: Int64 public var agentUserId: String @@ -17,6 +25,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { public var profile: LocalProfile public var fullPreferences: FullPreferences public var activeUser: Bool + public var activeOrder: Int64 public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } @@ -49,6 +58,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { profile: LocalProfile.sampleData, fullPreferences: FullPreferences.sampleData, activeUser: true, + activeOrder: 0, showNtfs: true, sendRcptsContacts: true, sendRcptsSmallGroups: false @@ -701,6 +711,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case voice case files case simplexLinks + case reports case history public var id: Self { self } @@ -721,6 +732,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: true case .files: true case .simplexLinks: true + case .reports: false case .history: false } } @@ -734,6 +746,7 @@ 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 .history: return NSLocalizedString("Visible history", comment: "chat feature") } } @@ -747,6 +760,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return "mic" case .files: return "doc" case .simplexLinks: return "link.circle" + case .reports: return "flag" case .history: return "clock" } } @@ -760,6 +774,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return "mic.fill" case .files: return "doc.fill" case .simplexLinks: return "link.circle.fill" + case .reports: return "flag.fill" case .history: return "clock.fill" } } @@ -809,6 +824,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .on: return "Allow to send SimpleX links." case .off: return "Prohibit sending SimpleX links." } + case .reports: + switch enabled { + case .on: return "Allow to report messsages to moderators." + case .off: return "Prohibit reporting messages to moderators." + } case .history: switch enabled { case .on: return "Send up to 100 last messages to new members." @@ -819,38 +839,43 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { switch self { case .timedMessages: switch enabled { - case .on: return "Group members can send disappearing messages." - case .off: return "Disappearing messages are prohibited in this group." + case .on: return "Members can send disappearing messages." + case .off: return "Disappearing messages are prohibited." } case .directMessages: switch enabled { - case .on: return "Group members can send direct messages." - case .off: return "Direct messages between members are prohibited in this group." + case .on: return "Members can send direct messages." + case .off: return "Direct messages between members are prohibited." } case .fullDelete: switch enabled { - case .on: return "Group members can irreversibly delete sent messages. (24 hours)" - case .off: return "Irreversible message deletion is prohibited in this group." + case .on: return "Members can irreversibly delete sent messages. (24 hours)" + case .off: return "Irreversible message deletion is prohibited." } case .reactions: switch enabled { - case .on: return "Group members can add message reactions." - case .off: return "Message reactions are prohibited in this group." + case .on: return "Members can add message reactions." + case .off: return "Message reactions are prohibited." } case .voice: switch enabled { - case .on: return "Group members can send voice messages." - case .off: return "Voice messages are prohibited in this group." + case .on: return "Members can send voice messages." + case .off: return "Voice messages are prohibited." } case .files: switch enabled { - case .on: return "Group members can send files and media." - case .off: return "Files and media are prohibited in this group." + case .on: return "Members can send files and media." + case .off: return "Files and media are prohibited." } case .simplexLinks: switch enabled { - case .on: return "Group members can send SimpleX links." - case .off: return "SimpleX links are prohibited in this group." + case .on: return "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 .off: return "Reporting messages to moderators is prohibited." } case .history: switch enabled { @@ -997,6 +1022,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { public var voice: RoleGroupPreference public var files: RoleGroupPreference public var simplexLinks: RoleGroupPreference + public var reports: GroupPreference public var history: GroupPreference public init( @@ -1007,6 +1033,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { voice: RoleGroupPreference, files: RoleGroupPreference, simplexLinks: RoleGroupPreference, + reports: GroupPreference, history: GroupPreference ) { self.timedMessages = timedMessages @@ -1016,6 +1043,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { self.voice = voice self.files = files self.simplexLinks = simplexLinks + self.reports = reports self.history = history } @@ -1027,6 +1055,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { voice: RoleGroupPreference(enable: .on, role: nil), files: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil), + reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on) ) } @@ -1039,6 +1068,7 @@ public struct GroupPreferences: Codable, Hashable { public var voice: RoleGroupPreference? public var files: RoleGroupPreference? public var simplexLinks: RoleGroupPreference? + public var reports: GroupPreference? public var history: GroupPreference? public init( @@ -1049,6 +1079,7 @@ public struct GroupPreferences: Codable, Hashable { voice: RoleGroupPreference? = nil, files: RoleGroupPreference? = nil, simplexLinks: RoleGroupPreference? = nil, + reports: GroupPreference? = nil, history: GroupPreference? = nil ) { self.timedMessages = timedMessages @@ -1058,6 +1089,7 @@ public struct GroupPreferences: Codable, Hashable { self.voice = voice self.files = files self.simplexLinks = simplexLinks + self.reports = reports self.history = history } @@ -1069,6 +1101,7 @@ public struct GroupPreferences: Codable, Hashable { voice: RoleGroupPreference(enable: .on, role: nil), files: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil), + reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on) ) } @@ -1082,6 +1115,7 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group voice: fullPreferences.voice, files: fullPreferences.files, simplexLinks: fullPreferences.simplexLinks, + reports: fullPreferences.reports, history: fullPreferences.history ) } @@ -1167,7 +1201,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case local(noteFolder: NoteFolder) case contactRequest(contactRequest: UserContactRequest) case contactConnection(contactConnection: PendingContactConnection) - case invalidJSON(json: String) + case invalidJSON(json: Data?) private static let invalidChatName = NSLocalizedString("invalid chat", comment: "invalid chat data") @@ -1299,6 +1333,19 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + get { + switch self { + case let .direct(contact): return contact.userCantSendReason + case let .group(groupInfo): return groupInfo.userCantSendReason + case let .local(noteFolder): return noteFolder.userCantSendReason + case let .contactRequest(contactRequest): return contactRequest.userCantSendReason + case let .contactConnection(contactConnection): return contactConnection.userCantSendReason + case .invalidJSON: return ("can't send messages", nil) + } + } + } + public var sendMsgEnabled: Bool { get { switch self { @@ -1332,6 +1379,13 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var contactCard: Bool { + switch self { + case let .direct(contact): contact.activeConn == nil && contact.profile.contactLink != nil && contact.active + default: false + } + } + public var groupInfo: GroupInfo? { switch self { case let .group(groupInfo): return groupInfo @@ -1430,9 +1484,17 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { return .other } } - - public var ntfsEnabled: Bool { - self.chatSettings?.enableNtfs == .all + + public func ntfsEnabled(chatItem: ChatItem) -> Bool { + ntfsEnabled(chatItem.meta.userMention) + } + + public func ntfsEnabled(_ userMention: Bool) -> Bool { + switch self.chatSettings?.enableNtfs { + case .all: true + case .mentions: userMention + default: false + } } public var chatSettings: ChatSettings? { @@ -1442,6 +1504,22 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { default: return nil } } + + public var nextNtfMode: MsgFilter? { + self.chatSettings?.enableNtfs.nextMode(mentions: hasMentions) + } + + public var hasMentions: Bool { + if case .group = self { true } else { false } + } + + public var chatTags: [Int64]? { + switch self { + case let .direct(contact): return contact.chatTags + case let .group(groupInfo): return groupInfo.chatTags + default: return nil + } + } var createdAt: Date { switch self { @@ -1475,6 +1553,24 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case .invalidJSON: return .now } } + + public func ttl(_ globalTTL: ChatItemTTL) -> ChatTTL { + switch self { + case let .direct(contact): + return if let ciTTL = contact.chatItemTTL { + ChatTTL.chat(ChatItemTTL(ciTTL)) + } else { + ChatTTL.userDefault(globalTTL) + } + case let .group(groupInfo): + return if let ciTTL = groupInfo.chatItemTTL { + ChatTTL.chat(ChatItemTTL(ciTTL)) + } else { + ChatTTL.userDefault(globalTTL) + } + default: return ChatTTL.userDefault(globalTTL) + } + } public struct SampleData: Hashable { public var direct: ChatInfo @@ -1506,7 +1602,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { self.chatStats = chatStats } - public static func invalidJSON(_ json: String) -> ChatData { + public static func invalidJSON(_ json: Data?) -> ChatData { ChatData( chatInfo: .invalidJSON(json: json), chatItems: [], @@ -1516,14 +1612,20 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { } public struct ChatStats: Decodable, Hashable { - public init(unreadCount: Int = 0, minUnreadItemId: Int64 = 0, unreadChat: Bool = false) { + public init(unreadCount: Int = 0, unreadMentions: Int = 0, reportsCount: Int = 0, minUnreadItemId: Int64 = 0, unreadChat: Bool = false) { self.unreadCount = unreadCount + self.unreadMentions = unreadMentions + self.reportsCount = reportsCount self.minUnreadItemId = minUnreadItemId self.unreadChat = unreadChat } public var unreadCount: Int = 0 + public var unreadMentions: Int = 0 + // actual only via getChats() and getChat(.initial), otherwise, zero + public var reportsCount: Int = 0 public var minUnreadItemId: Int64 = 0 + // actual only via getChats(), otherwise, false public var unreadChat: Bool = false } @@ -1543,6 +1645,8 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { var chatTs: Date? var contactGroupMemberId: Int64? var contactGrpInvSent: Bool + public var chatTags: [Int64] + public var chatItemTTL: Int64? public var uiThemes: ThemeModeOverrides? public var chatDeleted: Bool @@ -1551,15 +1655,16 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var ready: Bool { get { activeConn?.connStatus == .ready } } public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } public var active: Bool { get { contactStatus == .active } } - public var sendMsgEnabled: Bool { get { - ( - sndReady - && active - && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false) - && !(activeConn?.connDisabled ?? true) - ) - || nextSendGrpInv - } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + // TODO [short links] this will have additional statuses for pending contact requests before they are accepted + if nextSendGrpInv { return nil } + if !active { return ("contact deleted", nil) } + if !sndReady { return ("contact not ready", nil) } + if activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) } + if activeConn?.connDisabled ?? true { return ("contact disabled", nil) } + return nil + } + public var sendMsgEnabled: Bool { userCantSendReason == nil } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } @@ -1613,6 +1718,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { createdAt: .now, updatedAt: .now, contactGrpInvSent: false, + chatTags: [], chatDeleted: false ) } @@ -1676,7 +1782,7 @@ public struct Connection: Decodable, Hashable { static let sampleData = Connection( connId: 1, agentConnId: "abc", - peerChatVRange: VersionRange(minVersion: 1, maxVersion: 1), + peerChatVRange: VersionRange(1, 1), connStatus: .ready, connLevel: 0, viaGroupLink: false, @@ -1688,17 +1794,13 @@ public struct Connection: Decodable, Hashable { } public struct VersionRange: Decodable, Hashable { - public init(minVersion: Int, maxVersion: Int) { + public init(_ minVersion: Int, _ maxVersion: Int) { self.minVersion = minVersion self.maxVersion = maxVersion } public var minVersion: Int public var maxVersion: Int - - public func isCompatibleRange(_ vRange: VersionRange) -> Bool { - self.minVersion <= vRange.maxVersion && vRange.minVersion <= self.maxVersion - } } public struct SecurityCode: Decodable, Equatable, Hashable { @@ -1741,6 +1843,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable { public var id: ChatId { get { "<@\(contactRequestId)" } } public var apiId: Int64 { get { contactRequestId } } var ready: Bool { get { true } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } public var sendMsgEnabled: Bool { get { false } } public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } @@ -1750,7 +1853,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable { public static let sampleData = UserContactRequest( contactRequestId: 1, userContactLinkId: 1, - cReqChatVRange: VersionRange(minVersion: 1, maxVersion: 1), + cReqChatVRange: VersionRange(1, 1), localDisplayName: "alice", profile: Profile.sampleData, createdAt: .now, @@ -1765,7 +1868,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var viaContactUri: Bool public var groupLinkId: String? public var customUserProfileId: Int64? - public var connReqInv: String? + public var connLinkInv: CreatedConnLink? public var localAlias: String var createdAt: Date public var updatedAt: Date @@ -1773,6 +1876,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var id: ChatId { get { ":\(pccConnId)" } } public var apiId: Int64 { get { pccConnId } } var ready: Bool { get { false } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } public var sendMsgEnabled: Bool { get { false } } var localDisplayName: String { get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) } @@ -1780,9 +1884,11 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var displayName: String { get { if let initiated = pccConnStatus.initiated { - return initiated && !viaContactUri + return viaContactUri + ? NSLocalizedString("requested to connect", comment: "chat list item title") + : initiated ? NSLocalizedString("invited to connect", comment: "chat list item title") - : NSLocalizedString("connecting…", comment: "chat list item title") + : NSLocalizedString("accepted invitation", comment: "chat list item title") } else { // this should not be in the list return NSLocalizedString("connection established", comment: "chat list item title (it should not be shown") @@ -1850,6 +1956,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public enum ConnStatus: String, Decodable, Hashable { case new = "new" + case prepared = "prepared" case joined = "joined" case requested = "requested" case accepted = "accepted" @@ -1861,6 +1968,7 @@ public enum ConnStatus: String, Decodable, Hashable { get { switch self { case .new: return true + case .prepared: return false case .joined: return false case .requested: return true case .accepted: return true @@ -1886,9 +1994,9 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var groupId: Int64 var localDisplayName: GroupName public var groupProfile: GroupProfile + public var businessChat: BusinessChatInfo? public var fullGroupPreferences: FullGroupPreferences public var membership: GroupMember - public var hostConnCustomUserProfileId: Int64? public var chatSettings: ChatSettings var createdAt: Date var updatedAt: Date @@ -1898,13 +2006,28 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "#\(groupId)" } } public var apiId: Int64 { get { groupId } } public var ready: Bool { get { true } } - public var sendMsgEnabled: Bool { get { membership.memberActive } } - public var displayName: String { get { groupProfile.displayName } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + return if membership.memberActive { + membership.memberRole == .observer ? ("you are observer", "Please contact group admin.") : nil + } else { + switch membership.memberStatus { + case .memRejected: ("request to join rejected", nil) + case .memGroupDeleted: ("group is deleted", nil) + case .memRemoved: ("removed from group", nil) + case .memLeft: ("you left", nil) + default: ("can't send messages", nil) + } + } + } + public var sendMsgEnabled: Bool { userCantSendReason == nil } + public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } public var image: String? { get { groupProfile.image } } - public var localAlias: String { "" } + public var chatTags: [Int64] + public var chatItemTTL: Int64? + public var localAlias: String - public var canEdit: Bool { + public var isOwner: Bool { return membership.memberRole == .owner && membership.memberCurrent } @@ -1922,10 +2045,11 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { groupProfile: GroupProfile.sampleData, fullGroupPreferences: FullGroupPreferences.sampleData, membership: GroupMember.sampleData, - hostConnCustomUserProfileId: nil, chatSettings: ChatSettings.defaults, createdAt: .now, - updatedAt: .now + updatedAt: .now, + chatTags: [], + localAlias: "" ) } @@ -1956,6 +2080,17 @@ public struct GroupProfile: Codable, NamedChat, Hashable { ) } +public struct BusinessChatInfo: Decodable, Hashable { + public var chatType: BusinessChatType + public var businessId: String + public var customerId: String +} + +public enum BusinessChatType: String, Codable, Hashable { + case business + case customer +} + public struct GroupMember: Identifiable, Decodable, Hashable { public var groupMemberId: Int64 public var groupId: Int64 @@ -1971,8 +2106,17 @@ public struct GroupMember: Identifiable, Decodable, Hashable { public var memberContactId: Int64? public var memberContactProfileId: Int64 public var activeConn: Connection? + public var memberChatVRange: VersionRange public var id: String { "#\(groupId) @\(groupMemberId)" } + public var ready: Bool { get { activeConn?.connStatus == .ready } } + public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } + public var sendMsgEnabled: Bool { get { + sndReady + && memberCurrent + && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false) + && !(activeConn?.connDisabled ?? true) + } } public var displayName: String { get { let p = memberProfile @@ -2013,14 +2157,26 @@ public struct GroupMember: Identifiable, Decodable, Hashable { ? String.localizedStringWithFormat(NSLocalizedString("Past member %@", comment: "past/unknown group member"), name) : name } + + public var localAliasAndFullName: String { + get { + let p = memberProfile + let fullName = p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)") + let name = p.localAlias == "" ? fullName : "\(p.localAlias) (\(fullName))" + + return pastMember(name) + } + } public var memberActive: Bool { switch memberStatus { + case .memRejected: return false case .memRemoved: return false case .memLeft: return false case .memGroupDeleted: return false case .memUnknown: return false case .memInvited: return false + case .memPendingApproval: return true case .memIntroduced: return false case .memIntroInvited: return false case .memAccepted: return false @@ -2033,11 +2189,13 @@ public struct GroupMember: Identifiable, Decodable, Hashable { public var memberCurrent: Bool { switch memberStatus { + case .memRejected: return false case .memRemoved: return false case .memLeft: return false case .memGroupDeleted: return false case .memUnknown: return false case .memInvited: return false + case .memPendingApproval: return false case .memIntroduced: return true case .memIntroInvited: return true case .memAccepted: return true @@ -2057,7 +2215,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? { if !canBeRemoved(groupInfo: groupInfo) { return nil } let userRole = groupInfo.membership.memberRole - return GroupMemberRole.allCases.filter { $0 <= userRole && $0 != .author } + return GroupMemberRole.supportedRoles.filter { $0 <= userRole } } public func canBlockForAll(groupInfo: GroupInfo) -> Bool { @@ -2065,7 +2223,19 @@ public struct GroupMember: Identifiable, Decodable, Hashable { return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .admin && userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberActive } + + public var canReceiveReports: Bool { + memberRole >= .moderator && versionRange.maxVersion >= REPORTS_VERSION + } + public var versionRange: VersionRange { + if let activeConn { + activeConn.peerChatVRange + } else { + memberChatVRange + } + } + public var memberIncognito: Bool { memberProfile.profileId != memberContactProfileId } @@ -2084,7 +2254,8 @@ public struct GroupMember: Identifiable, Decodable, Hashable { memberProfile: LocalProfile.sampleData, memberContactId: 1, memberContactProfileId: 1, - activeConn: Connection.sampleData + activeConn: Connection.sampleData, + memberChatVRange: VersionRange(2, 12) ) } @@ -2103,19 +2274,23 @@ public struct GroupMemberIds: Decodable, Hashable { } public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Codable, Hashable { - case observer = "observer" - case author = "author" - case member = "member" - case admin = "admin" - case owner = "owner" + case observer + case author + case member + case moderator + case admin + case owner public var id: Self { self } + public static var supportedRoles: [GroupMemberRole] = [.observer, .member, .admin, .owner] + public var text: String { switch self { case .observer: return NSLocalizedString("observer", comment: "member role") case .author: return NSLocalizedString("author", comment: "member role") case .member: return NSLocalizedString("member", comment: "member role") + case .moderator: return NSLocalizedString("moderator", comment: "member role") case .admin: return NSLocalizedString("admin", comment: "member role") case .owner: return NSLocalizedString("owner", comment: "member role") } @@ -2123,11 +2298,12 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Cod private var comparisonValue: Int { switch self { - case .observer: return 0 - case .author: return 1 - case .member: return 2 - case .admin: return 3 - case .owner: return 4 + case .observer: 0 + case .author: 1 + case .member: 2 + case .moderator: 3 + case .admin: 4 + case .owner: 5 } } @@ -2145,11 +2321,13 @@ public enum GroupMemberCategory: String, Decodable, Hashable { } public enum GroupMemberStatus: String, Decodable, Hashable { + case memRejected = "rejected" case memRemoved = "removed" case memLeft = "left" case memGroupDeleted = "deleted" case memUnknown = "unknown" case memInvited = "invited" + case memPendingApproval = "pending_approval" case memIntroduced = "introduced" case memIntroInvited = "intro-inv" case memAccepted = "accepted" @@ -2160,11 +2338,13 @@ public enum GroupMemberStatus: String, Decodable, Hashable { public var text: LocalizedStringKey { switch self { + case .memRejected: return "rejected" case .memRemoved: return "removed" case .memLeft: return "left" case .memGroupDeleted: return "group deleted" case .memUnknown: return "unknown status" case .memInvited: return "invited" + case .memPendingApproval: return "pending approval" case .memIntroduced: return "connecting (introduced)" case .memIntroInvited: return "connecting (introduction invitation)" case .memAccepted: return "connecting (accepted)" @@ -2177,11 +2357,13 @@ public enum GroupMemberStatus: String, Decodable, Hashable { public var shortText: LocalizedStringKey { switch self { + case .memRejected: return "rejected" case .memRemoved: return "removed" case .memLeft: return "left" case .memGroupDeleted: return "group deleted" case .memUnknown: return "unknown" case .memInvited: return "invited" + case .memPendingApproval: return "pending" case .memIntroduced: return "connecting" case .memIntroInvited: return "connecting" case .memAccepted: return "connecting" @@ -2204,6 +2386,7 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "*\(noteFolderId)" } } public var apiId: Int64 { get { noteFolderId } } public var ready: Bool { get { true } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { nil } public var sendMsgEnabled: Bool { get { true } } public var displayName: String { get { ChatInfo.privateNotesChatName } } public var fullName: String { get { "" } } @@ -2238,41 +2421,105 @@ public struct MemberSubError: Decodable, Hashable { } public enum ConnectionEntity: Decodable, Hashable { - case rcvDirectMsgConnection(contact: Contact?) - case rcvGroupMsgConnection(groupInfo: GroupInfo, groupMember: GroupMember) - case sndFileConnection(sndFileTransfer: SndFileTransfer) - case rcvFileConnection(rcvFileTransfer: RcvFileTransfer) - case userContactConnection(userContact: UserContact) + case rcvDirectMsgConnection(entityConnection: Connection, contact: Contact?) + case rcvGroupMsgConnection(entityConnection: Connection, groupInfo: GroupInfo, groupMember: GroupMember) + case sndFileConnection(entityConnection: Connection, sndFileTransfer: SndFileTransfer) + case rcvFileConnection(entityConnection: Connection, rcvFileTransfer: RcvFileTransfer) + case userContactConnection(entityConnection: Connection, userContact: UserContact) public var id: String? { switch self { - case let .rcvDirectMsgConnection(contact): - return contact?.id - case let .rcvGroupMsgConnection(_, groupMember): - return groupMember.id - case let .userContactConnection(userContact): - return userContact.id + case let .rcvDirectMsgConnection(conn, contact): + contact?.id ?? conn.id + case let .rcvGroupMsgConnection(_, _, groupMember): + groupMember.id + case let .userContactConnection(_, userContact): + userContact.id default: - return nil + nil } } + + // public var localDisplayName: String? { + // switch self { + // case let .rcvDirectMsgConnection(conn, contact): + // if let name = contact?.localDisplayName { "@\(name)" } else { conn.id } + // case let .rcvGroupMsgConnection(_, g, m): + // "#\(g.localDisplayName) @\(m.localDisplayName)" + // case let .userContactConnection(_, userContact): + // userContact.id + // default: + // nil + // } + // } - public var ntfsEnabled: Bool { + public var conn: Connection { switch self { - case let .rcvDirectMsgConnection(contact): return contact?.chatSettings.enableNtfs == .all - case let .rcvGroupMsgConnection(groupInfo, _): return groupInfo.chatSettings.enableNtfs == .all - case .sndFileConnection: return false - case .rcvFileConnection: return false - case let .userContactConnection(userContact): return userContact.groupId == nil + case let .rcvDirectMsgConnection(entityConnection, _): entityConnection + case let .rcvGroupMsgConnection(entityConnection, _, _): entityConnection + case let .sndFileConnection(entityConnection, _): entityConnection + case let .rcvFileConnection(entityConnection, _): entityConnection + case let .userContactConnection(entityConnection, _): entityConnection } } } +public struct NtfConn: Decodable, Hashable { + public var user: User + public var agentConnId: String + public var agentDbQueueId: Int64 + public var connEntity: ConnectionEntity + public var expectedMsg_: NtfMsgInfo? +} + public struct NtfMsgInfo: Decodable, Hashable { public var msgId: String public var msgTs: Date } +public enum RcvNtfMsgInfo: Decodable { + case info(ntfMsgInfo: NtfMsgInfo?) + case error(ntfMsgError: AgentErrorType) + + @inline(__always) + public var noMsg: Bool { + if case let .info(msg) = self { msg == nil } else { true } + } + + @inline(__always) + public var isError: Bool { + if case .error = self { true } else { false } + } +} + +let iso8601DateFormatter = { + let f = ISO8601DateFormatter() + f.formatOptions = [.withInternetDateTime] + return f +}() + +// used in apiGetConnNtfMessages +public struct ConnMsgReq { + public var msgConnId: String + public var msgDbQueueId: Int64 + public var msgTs: Date // SystemTime encodes as a number, should be taken from NtfMsgInfo + + public init(msgConnId: String, msgDbQueueId: Int64, msgTs: Date) { + self.msgConnId = msgConnId + self.msgDbQueueId = msgDbQueueId + self.msgTs = msgTs + } + + public var cmdString: String { + "\(msgConnId):\(msgDbQueueId):\(iso8601DateFormatter.string(from: msgTs))" + } +} + +public struct NtfMsgAckInfo: Decodable, Hashable { + public var msgId: String + public var msgTs_: Date? +} + public struct ChatItemDeletion: Decodable, Hashable { public var deletedChatItem: AChatItem public var toChatItem: AChatItem? = nil @@ -2281,12 +2528,27 @@ public struct ChatItemDeletion: Decodable, Hashable { public struct AChatItem: Decodable, Hashable { public var chatInfo: ChatInfo public var chatItem: ChatItem +} - public var chatId: String { - if case let .groupRcv(groupMember) = chatItem.chatDir { - return groupMember.id - } - return chatInfo.id +public struct CIMentionMember: Decodable, Hashable { + public var groupMemberId: Int64 + public var displayName: String + public var localAlias: String? + public var memberRole: GroupMemberRole +} + +public struct CIMention: Decodable, Hashable { + public var memberId: String + public var memberRef: CIMentionMember? + + public init(groupMember m: GroupMember) { + self.memberId = m.memberId + self.memberRef = CIMentionMember( + groupMemberId: m.groupMemberId, + displayName: m.memberProfile.displayName, + localAlias: m.memberProfile.localAlias, + memberRole: m.memberRole + ) } } @@ -2295,6 +2557,11 @@ public struct ACIReaction: Decodable, Hashable { public var chatReaction: CIReaction } +public struct MemberReaction: Decodable, Hashable { + public var groupMember: GroupMember + public var reactionTs: Date +} + public struct CIReaction: Decodable, Hashable { public var chatDir: CIDirection public var chatItem: ChatItem @@ -2303,11 +2570,12 @@ public struct CIReaction: Decodable, Hashable { } public struct ChatItem: Identifiable, Decodable, Hashable { - public init(chatDir: CIDirection, meta: CIMeta, content: CIContent, formattedText: [FormattedText]? = nil, quotedItem: CIQuote? = nil, reactions: [CIReactionCount] = [], file: CIFile? = nil) { + public init(chatDir: CIDirection, meta: CIMeta, content: CIContent, formattedText: [FormattedText]? = nil, mentions: [String: CIMention]? = nil, quotedItem: CIQuote? = nil, reactions: [CIReactionCount] = [], file: CIFile? = nil) { self.chatDir = chatDir self.meta = meta self.content = content self.formattedText = formattedText + self.mentions = mentions self.quotedItem = quotedItem self.reactions = reactions self.file = file @@ -2317,6 +2585,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { public var meta: CIMeta public var content: CIContent public var formattedText: [FormattedText]? + public var mentions: [String: CIMention]? public var quotedItem: CIQuote? public var reactions: [CIReactionCount] public var file: CIFile? @@ -2325,7 +2594,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { public var isLiveDummy: Bool = false private enum CodingKeys: String, CodingKey { - case chatDir, meta, content, formattedText, quotedItem, reactions, file + case chatDir, meta, content, formattedText, mentions, quotedItem, reactions, file } public var id: Int64 { meta.itemId } @@ -2516,6 +2785,21 @@ public struct ChatItem: Identifiable, Decodable, Hashable { default: return true } } + + public var isReport: Bool { + switch content { + case let .sndMsgContent(msgContent), let .rcvMsgContent(msgContent): + switch msgContent { + case .report: true + default: false + } + default: false + } + } + + public var isActiveReport: Bool { + isReport && !isDeletedContent && meta.itemDeleted == nil + } public var canBeDeletedForSelf: Bool { (content.msgContent != nil && !meta.isLive) || meta.itemDeleted != nil || isDeletedContent || mergeCategory != nil || showLocalDelete @@ -2601,6 +2885,35 @@ public struct ChatItem: Identifiable, Decodable, Hashable { file: nil ) } + + public static func getReportSample(text: String, reason: ReportReason, item: ChatItem, sender: GroupMember? = nil) -> ChatItem { + let chatDir = if let sender = sender { + CIDirection.groupRcv(groupMember: sender) + } else { + CIDirection.groupSnd + } + + return ChatItem( + chatDir: chatDir, + meta: CIMeta( + itemId: -2, + itemTs: .now, + itemText: "", + itemStatus: .rcvRead, + createdAt: .now, + updatedAt: .now, + itemDeleted: nil, + itemEdited: false, + itemLive: false, + userMention: false, + deletable: false, + editable: false + ), + content: .sndMsgContent(msgContent: .report(text: text, reason: reason)), + quotedItem: CIQuote.getSample(item.id, item.meta.createdAt, item.text, chatDir: item.chatDir), + file: nil + ) + } public static func deletedItemDummy() -> ChatItem { ChatItem( @@ -2615,6 +2928,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { itemDeleted: nil, itemEdited: false, itemLive: false, + userMention: false, deletable: false, editable: false ), @@ -2637,6 +2951,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { itemDeleted: nil, itemEdited: false, itemLive: true, + userMention: false, deletable: false, editable: false ), @@ -2648,7 +2963,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { return item } - public static func invalidJSON(chatDir: CIDirection?, meta: CIMeta?, json: String) -> ChatItem { + public static func invalidJSON(chatDir: CIDirection?, meta: CIMeta?, json: Data?) -> ChatItem { ChatItem( chatDir: chatDir ?? .directSnd, meta: meta ?? .invalidJSON, @@ -2688,6 +3003,13 @@ public enum CIDirection: Decodable, Hashable { } } } + + public func sameDirection(_ dir: CIDirection) -> Bool { + switch (self, dir) { + case let (.groupRcv(m1), .groupRcv(m2)): m1.groupMemberId == m2.groupMemberId + default: sent == dir.sent + } + } } public struct CIMeta: Decodable, Hashable { @@ -2703,10 +3025,11 @@ public struct CIMeta: Decodable, Hashable { public var itemEdited: Bool public var itemTimed: CITimed? public var itemLive: Bool? + public var userMention: Bool public var deletable: Bool public var editable: Bool - public var timestampText: Text { get { formatTimestampText(itemTs) } } + public var timestampText: Text { Text(formatTimestampMeta(itemTs)) } public var recent: Bool { updatedAt + 10 > .now } public var isLive: Bool { itemLive == true } public var disappearing: Bool { !isRcvNew && itemTimed?.deleteAt != nil } @@ -2716,10 +3039,6 @@ public struct CIMeta: Decodable, Hashable { return false } - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color)? { - itemStatus.statusIcon(metaColor, primaryColor) - } - public static func getSample(_ id: Int64, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, deletable: Bool = true, editable: Bool = true) -> CIMeta { CIMeta( itemId: id, @@ -2731,6 +3050,7 @@ public struct CIMeta: Decodable, Hashable { itemDeleted: itemDeleted, itemEdited: itemEdited, itemLive: itemLive, + userMention: false, deletable: deletable, editable: editable ) @@ -2747,6 +3067,7 @@ public struct CIMeta: Decodable, Hashable { itemDeleted: nil, itemEdited: false, itemLive: false, + userMention: false, deletable: false, editable: false ) @@ -2760,9 +3081,20 @@ public struct CITimed: Decodable, Hashable { let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) +let msgDateYearFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits) public func formatTimestampText(_ date: Date) -> Text { - return Text(date, format: recent(date) ? msgTimeFormat : msgDateFormat) + Text(verbatim: date.formatted( + recent(date) + ? msgTimeFormat + : Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) + ? msgDateFormat + : msgDateYearFormat + )) +} + +public func formatTimestampMeta(_ date: Date) -> String { + date.formatted(date: .omitted, time: .shortened) } private func recent(_ date: Date) -> Bool { @@ -2804,22 +3136,37 @@ public enum CIStatus: Decodable, Hashable { case .invalid: return "invalid" } } - - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color)? { + + public var sent: Bool { switch self { - case .sndNew: return nil - case .sndSent: return ("checkmark", metaColor) - case let .sndRcvd(msgRcptStatus, _): + case .sndNew: true + case .sndSent: true + case .sndRcvd: true + case .sndErrorAuth: true + case .sndError: true + case .sndWarning: true + case .rcvNew: false + case .rcvRead: false + case .invalid: false + } + } + + public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { + switch self { + case .sndNew: nil + case let .sndSent(sndProgress): + (Image("checkmark.wide"), sndProgress == .partial ? paleMetaColor : metaColor) + case let .sndRcvd(msgRcptStatus, sndProgress): switch msgRcptStatus { - case .ok: return ("checkmark", metaColor) - case .badMsgHash: return ("checkmark", .red) + case .ok: (Image("checkmark.2"), sndProgress == .partial ? paleMetaColor : metaColor) + case .badMsgHash: (Image("checkmark.2"), .red) } - case .sndErrorAuth: return ("multiply", .red) - case .sndError: return ("multiply", .red) - case .sndWarning: return ("exclamationmark.triangle.fill", .orange) - case .rcvNew: return ("circlebadge.fill", primaryColor) - case .rcvRead: return nil - case .invalid: return ("questionmark", metaColor) + case .sndErrorAuth: (Image(systemName: "multiply"), .red) + case .sndError: (Image(systemName: "multiply"), .red) + case .sndWarning: (Image(systemName: "exclamationmark.triangle.fill"), .orange) + case .rcvNew: (Image(systemName: "circlebadge.fill"), primaryColor) + case .rcvRead: nil + case .invalid: (Image(systemName: "questionmark"), metaColor) } } @@ -2848,6 +3195,13 @@ public enum CIStatus: Decodable, Hashable { ) } } + + public var isSndRcvd: Bool { + switch self { + case .sndRcvd: return true + default: return false + } + } } public enum SndError: Decodable, Hashable { @@ -2859,7 +3213,7 @@ public enum SndError: Decodable, Hashable { case proxyRelay(proxyServer: String, srvError: SrvError) case other(sndError: String) - public var errorInfo: String { + public var errorInfo: String { switch self { case .auth: NSLocalizedString("Wrong key or unknown connection - most likely this connection is deleted.", comment: "snd error text") case .quota: NSLocalizedString("Capacity exceeded - recipient did not receive previously sent messages.", comment: "snd error text") @@ -2914,20 +3268,20 @@ public enum GroupSndStatus: Decodable, Hashable { case warning(agentError: SndError) case invalid(text: String) - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color) { + public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color) { switch self { - case .new: return ("ellipsis", metaColor) - case .forwarded: return ("chevron.forward.2", metaColor) - case .inactive: return ("person.badge.minus", metaColor) - case .sent: return ("checkmark", metaColor) + case .new: (Image(systemName: "ellipsis"), metaColor) + case .forwarded: (Image(systemName: "chevron.forward.2"), metaColor) + case .inactive: (Image(systemName: "person.badge.minus"), metaColor) + case .sent: (Image("checkmark.wide"), metaColor) case let .rcvd(msgRcptStatus): switch msgRcptStatus { - case .ok: return ("checkmark", metaColor) - case .badMsgHash: return ("checkmark", .red) + case .ok: (Image("checkmark.2"), metaColor) + case .badMsgHash: (Image("checkmark.2"), .red) } - case .error: return ("multiply", .red) - case .warning: return ("exclamationmark.triangle.fill", .orange) - case .invalid: return ("questionmark", metaColor) + case .error: (Image(systemName: "multiply"), .red) + case .warning: (Image(systemName: "exclamationmark.triangle.fill"), .orange) + case .invalid: (Image(systemName: "questionmark"), metaColor) } } @@ -2994,6 +3348,20 @@ public enum CIForwardedFrom: Decodable, Hashable { } } + public var chatTypeApiIdMsgId: (ChatType, Int64, ChatItem.ID?)? { + switch self { + case .unknown: nil + case let .contact(_, _, contactId, msgId): + if let contactId { + (ChatType.direct, contactId, msgId) + } else { nil } + case let .group(_, _, groupId, msgId): + if let groupId { + (ChatType.group, groupId, msgId) + } else { nil } + } + } + public func text(_ chatType: ChatType) -> LocalizedStringKey { chatType == .local ? (chatName == "" ? "saved" : "saved from \(chatName)") @@ -3004,6 +3372,7 @@ public enum CIForwardedFrom: Decodable, Hashable { public enum CIDeleteMode: String, Decodable, Hashable { case cidmBroadcast = "broadcast" case cidmInternal = "internal" + case cidmInternalMark = "internalMark" } protocol ItemContent { @@ -3041,7 +3410,7 @@ public enum CIContent: Decodable, ItemContent, Hashable { case rcvDirectE2EEInfo(e2eeInfo: E2EEInfo) case sndGroupE2EEInfo(e2eeInfo: E2EEInfo) case rcvGroupE2EEInfo(e2eeInfo: E2EEInfo) - case invalidJSON(json: String) + case invalidJSON(json: Data?) public var text: String { get { @@ -3144,6 +3513,13 @@ public enum CIContent: Decodable, ItemContent, Hashable { default: return false } } + + public var isSndCall: Bool { + switch self { + case .sndCall: return true + default: return false + } + } } public enum MsgDecryptError: String, Decodable, Hashable { @@ -3171,14 +3547,12 @@ public struct CIQuote: Decodable, ItemContent, Hashable { public var sentAt: Date public var content: MsgContent public var formattedText: [FormattedText]? - public var text: String { switch (content.text, content) { case let ("", .voice(_, duration)): return durationText(duration) default: return content.text } } - public func getSender(_ membership: GroupMember?) -> String? { switch (chatDir) { case .directSnd: return "you" @@ -3242,9 +3616,11 @@ public enum MREmojiChar: String, Codable, CaseIterable, Hashable { case thumbsup = "👍" case thumbsdown = "👎" case smile = "😀" + case laugh = "😂" case sad = "😢" case heart = "❤" case launch = "🚀" + case check = "✅" } extension MsgReaction: Decodable { @@ -3254,8 +3630,12 @@ extension MsgReaction: Decodable { let type = try container.decode(String.self, forKey: CodingKeys.type) switch type { case "emoji": - let emoji = try container.decode(MREmojiChar.self, forKey: CodingKeys.emoji) - self = .emoji(emoji: emoji) + do { + let emoji = try container.decode(MREmojiChar.self, forKey: CodingKeys.emoji) + self = .emoji(emoji: emoji) + } catch { + self = .unknown(type: "emoji") + } default: self = .unknown(type: type) } @@ -3511,6 +3891,7 @@ public enum CIFileStatus: Decodable, Equatable, Hashable { public enum FileError: Decodable, Equatable, Hashable { case auth + case blocked(server: String, blockInfo: BlockingInfo) case noFile case relay(srvError: SrvError) case other(fileError: String) @@ -3518,6 +3899,7 @@ public enum FileError: Decodable, Equatable, Hashable { var id: String { switch self { case .auth: return "auth" + case let .blocked(srv, info): return "blocked \(srv) \(info)" case .noFile: return "noFile" case let .relay(srvError): return "relay \(srvError)" case let .other(fileError): return "other \(fileError)" @@ -3527,11 +3909,19 @@ public enum FileError: Decodable, Equatable, Hashable { public var errorInfo: String { switch self { case .auth: NSLocalizedString("Wrong key or unknown file chunk address - most likely file is deleted.", comment: "file error text") + case let .blocked(_, info): String.localizedStringWithFormat(NSLocalizedString("File is blocked by server operator:\n%@.", comment: "file error text"), info.reason.text) case .noFile: NSLocalizedString("File not found - most likely file was deleted or cancelled.", comment: "file error text") case let .relay(srvError): String.localizedStringWithFormat(NSLocalizedString("File server error: %@", comment: "file error text"), srvError.errorInfo) case let .other(fileError): String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "file error text"), fileError) } } + + public var moreInfoButton: (label: LocalizedStringKey, link: URL)? { + switch self { + case .blocked: ("How it works", contentModerationPostLink) + default: nil + } + } } public enum MsgContent: Equatable, Hashable { @@ -3541,6 +3931,7 @@ public enum MsgContent: Equatable, Hashable { case video(text: String, image: String, duration: Int) case voice(text: String, duration: Int) case file(String) + case report(text: String, reason: ReportReason) // TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift case unknown(type: String, text: String) @@ -3552,6 +3943,7 @@ public enum MsgContent: Equatable, Hashable { case let .video(text, _, _): return text case let .voice(text, _): return text case let .file(text): return text + case let .report(text, _): return text case let .unknown(_, text): return text } } @@ -3601,7 +3993,7 @@ public enum MsgContent: Equatable, Hashable { } } - var cmdString: String { + public var cmdString: String { "json \(encodeJSON(self))" } @@ -3611,6 +4003,7 @@ public enum MsgContent: Equatable, Hashable { case preview case image case duration + case reason } public static func == (lhs: MsgContent, rhs: MsgContent) -> Bool { @@ -3621,6 +4014,7 @@ public enum MsgContent: Equatable, Hashable { case let (.video(lt, li, ld), .video(rt, ri, rd)): return lt == rt && li == ri && ld == rd case let (.voice(lt, ld), .voice(rt, rd)): return lt == rt && ld == rd case let (.file(lf), .file(rf)): return lf == rf + case let (.report(lt, lr), .report(rt, rr)): return lt == rt && lr == rr case let (.unknown(lType, lt), .unknown(rType, rt)): return lType == rType && lt == rt default: return false } @@ -3656,6 +4050,10 @@ extension MsgContent: Decodable { case "file": let text = try container.decode(String.self, forKey: CodingKeys.text) self = .file(text) + case "report": + let text = try container.decode(String.self, forKey: CodingKeys.text) + let reason = try container.decode(ReportReason.self, forKey: CodingKeys.reason) + self = .report(text: text, reason: reason) default: let text = try? container.decode(String.self, forKey: CodingKeys.text) self = .unknown(type: type, text: text ?? "unknown message format") @@ -3693,6 +4091,10 @@ extension MsgContent: Encodable { case let .file(text): try container.encode("file", forKey: .type) try container.encode(text, forKey: .text) + case let .report(text, reason): + try container.encode("report", forKey: .type) + try container.encode(text, forKey: .text) + try container.encode(reason, forKey: .reason) // TODO use original JSON and type case let .unknown(_, text): try container.encode("text", forKey: .type) @@ -3705,6 +4107,12 @@ public struct FormattedText: Decodable, Hashable { public var text: String public var format: Format? + public static func plain(_ text: String) -> [FormattedText] { + text.isEmpty + ? [] + : [FormattedText(text: text, format: nil)] + } + public var isSecret: Bool { if case .secret = format { true } else { false } } @@ -3719,6 +4127,7 @@ public enum Format: Decodable, Equatable, Hashable { case colored(color: FormatColor) case uri case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String]) + case mention(memberName: String) case email case phone @@ -3736,12 +4145,14 @@ public enum SimplexLinkType: String, Decodable, Hashable { case contact case invitation case group + case channel public var description: String { switch self { case .contact: return NSLocalizedString("SimpleX contact address", comment: "simplex link type") case .invitation: return NSLocalizedString("SimpleX one-time invitation", comment: "simplex link type") case .group: return NSLocalizedString("SimpleX group link", comment: "simplex link type") + case .channel: return NSLocalizedString("SimpleX channel link", comment: "simplex link type") } } } @@ -3756,18 +4167,75 @@ public enum FormatColor: String, Decodable, Hashable { case black = "black" case white = "white" - public var uiColor: Color { - get { - switch (self) { - case .red: return .red - case .green: return .green - case .blue: return .blue - case .yellow: return .yellow - case .cyan: return .cyan - case .magenta: return .purple - case .black: return .primary - case .white: return .primary - } + public var uiColor: Color? { + switch (self) { + case .red: .red + case .green: .green + case .blue: .blue + case .yellow: .yellow + case .cyan: .cyan + case .magenta: .purple + case .black: nil + case .white: nil + } + } +} + +public enum ReportReason: Hashable { + case spam + case illegal + case community + case profile + case other + case unknown(type: String) + + public static var supportedReasons: [ReportReason] = [.spam, .illegal, .community, .profile, .other] + + public var text: String { + switch self { + case .spam: return NSLocalizedString("Spam", comment: "report reason") + case .illegal: return NSLocalizedString("Inappropriate content", comment: "report reason") + case .community: return NSLocalizedString("Community guidelines violation", comment: "report reason") + case .profile: return NSLocalizedString("Inappropriate profile", comment: "report reason") + case .other: return NSLocalizedString("Another reason", comment: "report reason") + case let .unknown(type): return type + } + } + + public var attrString: NSAttributedString { + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + return NSAttributedString(string: text.isEmpty ? self.text : "\(self.text): ", attributes: [ + .font: UIFont(descriptor: descr.withSymbolicTraits(.traitItalic) ?? descr, size: 0), + .foregroundColor: UIColor(Color.red) + ]) + } +} + +extension ReportReason: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .spam: try container.encode("spam") + case .illegal: try container.encode("illegal") + case .community: try container.encode("community") + case .profile: try container.encode("profile") + case .other: try container.encode("other") + case let .unknown(type): try container.encode(type) + } + } +} + +extension ReportReason: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let type = try container.decode(String.self) + switch type { + case "spam": self = .spam + case "illegal": self = .illegal + case "community": self = .community + case "profile": self = .profile + case "other": self = .other + default: self = .unknown(type: type) } } } @@ -3792,9 +4260,61 @@ public enum NtfTknStatus: String, Decodable, Hashable { case new = "NEW" case registered = "REGISTERED" case invalid = "INVALID" + case invalidBad = "INVALID,BAD" + case invalidTopic = "INVALID,TOPIC" + case invalidExpired = "INVALID,EXPIRED" + case invalidUnregistered = "INVALID,UNREGISTERED" case confirmed = "CONFIRMED" case active = "ACTIVE" case expired = "EXPIRED" + + public var workingToken: Bool { + switch self { + case .new: true + case .registered: true + case .invalid: false + case .invalidBad: false + case .invalidTopic: false + case .invalidExpired: false + case .invalidUnregistered: false + case .confirmed: true + case .active: true + case .expired: false + } + } + + public var text: String { + switch self { + case .new: NSLocalizedString("New", comment: "token status text") + case .registered: NSLocalizedString("Registered", comment: "token status text") + case .invalid: NSLocalizedString("Invalid", comment: "token status text") + case .invalidBad: NSLocalizedString("Invalid (bad token)", comment: "token status text") + case .invalidTopic: NSLocalizedString("Invalid (wrong topic)", comment: "token status text") + case .invalidExpired: NSLocalizedString("Invalid (expired)", comment: "token status text") + case .invalidUnregistered: NSLocalizedString("Invalid (unregistered)", comment: "token status text") + case .confirmed: NSLocalizedString("Confirmed", comment: "token status text") + case .active: NSLocalizedString("Active", comment: "token status text") + case .expired: NSLocalizedString("Expired", comment: "token status text") + } + } + + public func info(register: Bool) -> String { + switch self { + case .new: return NSLocalizedString("Please wait for token to be registered.", comment: "token info") + case .registered: fallthrough + case .confirmed: return NSLocalizedString("Please wait for token activation to complete.", comment: "token info") + case .active: return NSLocalizedString("You should receive notifications.", comment: "token info") + case .invalid: fallthrough + case .invalidBad: fallthrough + case .invalidTopic: fallthrough + case .invalidExpired: fallthrough + case .invalidUnregistered: fallthrough + case .expired: + return register + ? NSLocalizedString("Register notification token?", comment: "token info") + : NSLocalizedString("Please try to disable and re-enable notfications.", comment: "token info") + } + } } public struct SndFileTransfer: Decodable, Hashable { @@ -4086,45 +4606,53 @@ public enum ChatItemTTL: Identifiable, Comparable, Hashable { case day case week case month + case year case seconds(_ seconds: Int64) case none - public static var values: [ChatItemTTL] { [.none, .month, .week, .day] } + public static var values: [ChatItemTTL] { [.none, .year, .month, .week, .day] } public var id: Self { self } - public init(_ seconds: Int64?) { + public init(_ seconds: Int64) { switch seconds { + case 0: self = .none case 86400: self = .day case 7 * 86400: self = .week case 30 * 86400: self = .month - case let .some(n): self = .seconds(n) - case .none: self = .none + case 365 * 86400: self = .year + default: self = .seconds(seconds) } } - public var deleteAfterText: LocalizedStringKey { + public var deleteAfterText: String { switch self { - case .day: return "1 day" - case .week: return "1 week" - case .month: return "1 month" - case let .seconds(seconds): return "\(seconds) second(s)" - case .none: return "never" + case .day: return NSLocalizedString("1 day", comment: "delete after time") + case .week: return NSLocalizedString("1 week", comment: "delete after time") + case .month: return NSLocalizedString("1 month", comment: "delete after time") + case .year: return NSLocalizedString("1 year", comment: "delete after time") + case let .seconds(seconds): return String.localizedStringWithFormat(NSLocalizedString("%d seconds(s)", comment: "delete after time"), seconds) + case .none: return NSLocalizedString("never", comment: "delete after time") } } - public var seconds: Int64? { + public var seconds: Int64 { switch self { case .day: return 86400 case .week: return 7 * 86400 case .month: return 30 * 86400 + case .year: return 365 * 86400 case let .seconds(seconds): return seconds - case .none: return nil + case .none: return 0 } } private var comparisonValue: Int64 { - self.seconds ?? Int64.max + if self.seconds == 0 { + return Int64.max + } else { + return self.seconds + } } public static func < (lhs: Self, rhs: Self) -> Bool { @@ -4132,6 +4660,57 @@ public enum ChatItemTTL: Identifiable, Comparable, Hashable { } } +public enum ChatTTL: Identifiable, Hashable { + case userDefault(ChatItemTTL) + case chat(ChatItemTTL) + + public var id: Self { self } + + public var text: String { + switch self { + case let .chat(ttl): return ttl.deleteAfterText + case let .userDefault(ttl): return String.localizedStringWithFormat( + NSLocalizedString("default (%@)", comment: "delete after time"), + ttl.deleteAfterText) + } + } + + public var neverExpires: Bool { + switch self { + case let .chat(ttl): return ttl.seconds == 0 + case let .userDefault(ttl): return ttl.seconds == 0 + } + } + + public var value: Int64? { + switch self { + case let .chat(ttl): return ttl.seconds + case .userDefault: return nil + } + } + + public var usingDefault: Bool { + switch self { + case .userDefault: return true + case .chat: return false + } + } +} + +public struct ChatTag: Decodable, Hashable { + public var chatTagId: Int64 + public var chatTagText: String + public var chatTagEmoji: String? + + public var id: Int64 { chatTagId } + + public init(chatTagId: Int64, chatTagText: String, chatTagEmoji: String?) { + self.chatTagId = chatTagId + self.chatTagText = chatTagText + self.chatTagEmoji = chatTagEmoji + } +} + public struct ChatItemInfo: Decodable, Hashable { public var itemVersions: [ChatItemVersion] public var memberDeliveryStatuses: [MemberDeliveryStatus]? diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index 5f56180918..6cbc76ec98 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -27,6 +27,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 .reports: p.reports.on } } else { return true @@ -93,7 +94,12 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool { public func chatIconName(_ cInfo: ChatInfo) -> String { switch cInfo { case .direct: "person.crop.circle.fill" - case .group: "person.2.circle.fill" + case let .group(groupInfo): + switch groupInfo.businessChat?.chatType { + case .none: "person.2.circle.fill" + case .business: "briefcase.circle.fill" + case .customer: "person.crop.circle.fill" + } case .local: "folder.circle.fill" case .contactRequest: "person.crop.circle.fill" default: "circle.fill" diff --git a/apps/ios/SimpleXChat/CryptoFile.swift b/apps/ios/SimpleXChat/CryptoFile.swift index 0e539ba97c..dfe833f832 100644 --- a/apps/ios/SimpleXChat/CryptoFile.swift +++ b/apps/ios/SimpleXChat/CryptoFile.swift @@ -18,10 +18,10 @@ public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs { memcpy(ptr, (data as NSData).bytes, data.count) var cPath = path.cString(using: .utf8)! let cjson = chat_write_file(getChatCtrl(), &cPath, ptr, Int32(data.count))! - let d = fromCString(cjson).data(using: .utf8)! + let d = dataFromCString(cjson)! // TODO [unsafe] switch try jsonDecoder.decode(WriteFileResult.self, from: d) { case let .result(cfArgs): return cfArgs - case let .error(err): throw RuntimeError(err) + case let .error(err): throw RuntimeError(err) // TODO [unsafe] } } @@ -51,10 +51,10 @@ public func encryptCryptoFile(fromPath: String, toPath: String) throws -> Crypto var cFromPath = fromPath.cString(using: .utf8)! var cToPath = toPath.cString(using: .utf8)! let cjson = chat_encrypt_file(getChatCtrl(), &cFromPath, &cToPath)! - let d = fromCString(cjson).data(using: .utf8)! + let d = dataFromCString(cjson)! // TODO [unsafe] switch try jsonDecoder.decode(WriteFileResult.self, from: d) { case let .result(cfArgs): return cfArgs - case let .error(err): throw RuntimeError(err) + case let .error(err): throw RuntimeError(err) // TODO [unsafe] } } diff --git a/apps/ios/SimpleXChat/ErrorAlert.swift b/apps/ios/SimpleXChat/ErrorAlert.swift index 5b9acc4fca..a433d2313b 100644 --- a/apps/ios/SimpleXChat/ErrorAlert.swift +++ b/apps/ios/SimpleXChat/ErrorAlert.swift @@ -37,22 +37,18 @@ public struct ErrorAlert: Error { } public init(_ error: any Error) { - self = if let chatResponse = error as? ChatResponse { - ErrorAlert(chatResponse) + self = if let e = error as? ChatError { + ErrorAlert(e) } else { - ErrorAlert(LocalizedStringKey(error.localizedDescription)) + ErrorAlert("\(error.localizedDescription)") } } public init(_ chatError: ChatError) { - self = ErrorAlert("\(chatErrorString(chatError))") - } - - public init(_ chatResponse: ChatResponse) { - self = if let networkErrorAlert = getNetworkErrorAlert(chatResponse) { + self = if let networkErrorAlert = getNetworkErrorAlert(chatError) { networkErrorAlert } else { - ErrorAlert("\(responseError(chatResponse))") + ErrorAlert("\(chatErrorString(chatError))") } } } @@ -94,22 +90,21 @@ extension View { } } -public func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? { - switch r { - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))): - return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))): - return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .HOST))): - return ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))): - return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") - case let .chatCmdError(_, .errorAgent(.SMP(serverAddress, .PROXY(proxyErr)))): - return smpProxyErrorAlert(proxyErr, serverAddress) - case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr))))): - return proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer) - default: - return nil +public func getNetworkErrorAlert(_ e: ChatError) -> ErrorAlert? { + switch e { + case let .errorAgent(.BROKER(addr, .TIMEOUT)): + ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") + case let .errorAgent(.BROKER(addr, .NETWORK)): + ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") + case let .errorAgent(.BROKER(addr, .HOST)): + ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).") + case let .errorAgent(.BROKER(addr, .TRANSPORT(.version))): + ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") + case let .errorAgent(.SMP(serverAddress, .PROXY(proxyErr))): + smpProxyErrorAlert(proxyErr, serverAddress) + case let .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr)))): + proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer) + default: nil } } diff --git a/apps/ios/SimpleXChat/FileUtils.swift b/apps/ios/SimpleXChat/FileUtils.swift index 8b0d082aed..2341eb4a4f 100644 --- a/apps/ios/SimpleXChat/FileUtils.swift +++ b/apps/ios/SimpleXChat/FileUtils.swift @@ -41,7 +41,7 @@ public func getDocumentsDirectory() -> URL { FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! } -func getGroupContainerDirectory() -> URL { +public func getGroupContainerDirectory() -> URL { FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)! } diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index 67218a781e..be43158bc1 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -100,7 +100,7 @@ public func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64, hasAlpha return data } -public func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) -> String? { +public func resizeImageToStrSizeSync(_ image: UIImage, maxDataSize: Int64) -> String? { var img = image let hasAlpha = imageHasAlpha(image) var str = compressImageStr(img, hasAlpha: hasAlpha) @@ -116,7 +116,15 @@ public func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) -> String return str } +public func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) async -> String? { + resizeImageToStrSizeSync(image, maxDataSize: maxDataSize) +} + public func compressImageStr(_ image: UIImage, _ compressionQuality: CGFloat = 0.85, hasAlpha: Bool) -> String? { +// // Heavy workload to verify if UI gets blocked by the call +// for i in 0..<100 { +// print(image.jpegData(compressionQuality: Double(i) / 100)?.count ?? 0, terminator: ", ") +// } let ext = hasAlpha ? "png" : "jpg" if let data = hasAlpha ? image.pngData() : image.jpegData(compressionQuality: compressionQuality) { return "data:image/\(ext);base64,\(data.base64EncodedString())" @@ -130,7 +138,7 @@ private func reduceSize(_ image: UIImage, ratio: CGFloat, hasAlpha: Bool) -> UII return resizeImage(image, newBounds: bounds, drawIn: bounds, hasAlpha: hasAlpha) } -private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage { +public func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage { let format = UIGraphicsImageRendererFormat() format.scale = 1.0 format.opaque = !hasAlpha @@ -259,17 +267,26 @@ public func saveWallpaperFile(image: UIImage) -> String? { public func removeWallpaperFile(fileName: String? = nil) { do { - try FileManager.default.contentsOfDirectory(atPath: getWallpaperDirectory().path).forEach { - if URL(fileURLWithPath: $0).lastPathComponent == fileName { try FileManager.default.removeItem(atPath: $0) } + try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: getWallpaperDirectory().path), includingPropertiesForKeys: nil, options: []).forEach { url in + if url.lastPathComponent == fileName { + try FileManager.default.removeItem(at: url) + } } } catch { - logger.error("FileUtils.removeWallpaperFile error: \(error.localizedDescription)") + logger.error("FileUtils.removeWallpaperFile error: \(error)") } if let fileName { WallpaperType.cachedImages.removeValue(forKey: fileName) } } +public func removeWallpaperFilesFromTheme(_ theme: ThemeModeOverrides?) { + if let theme { + removeWallpaperFile(fileName: theme.light?.wallpaper?.imageFile) + removeWallpaperFile(fileName: theme.dark?.wallpaper?.imageFile) + } +} + public func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String { uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath) } @@ -383,16 +400,34 @@ extension UIImage { } return self } +} - public convenience init?(base64Encoded: String?) { - if let base64Encoded, let data = Data(base64Encoded: dropImagePrefix(base64Encoded)) { - self.init(data: data) +public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { + if let base64Encoded { + if let img = imageCache.object(forKey: base64Encoded as NSString) { + return img + } else if let data = Data(base64Encoded: dropImagePrefix(base64Encoded)), + let img = UIImage(data: data) { + imageCacheQueue.async { + imageCache.setObject(img, forKey: base64Encoded as NSString) + } + return img } else { return nil } + } else { + return nil } } +private let imageCacheQueue = DispatchQueue.global(qos: .background) + +private var imageCache: NSCache = { + var cache = NSCache() + cache.countLimit = 1000 + return cache +}() + public func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) { logger.debug("getLinkMetadata: fetching URL preview") LPMetadataProvider().startFetchingMetadata(for: url){ metadata, error in @@ -408,7 +443,7 @@ public func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) { logger.error("Couldn't load image preview from link metadata with error: \(error.localizedDescription)") } else { if let image = object as? UIImage, - let resized = resizeImageToStrSize(image, maxDataSize: 14000), + let resized = resizeImageToStrSizeSync(image, maxDataSize: 14000), let title = metadata.title, let uri = metadata.originalURL { linkPreview = LinkPreview(uri: uri, title: title, image: resized) diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift index 4b43595372..5579449caa 100644 --- a/apps/ios/SimpleXChat/Notifications.swift +++ b/apps/ios/SimpleXChat/Notifications.swift @@ -15,13 +15,14 @@ public let ntfCategoryContactConnected = "NTF_CAT_CONTACT_CONNECTED" public let ntfCategoryMessageReceived = "NTF_CAT_MESSAGE_RECEIVED" public let ntfCategoryCallInvitation = "NTF_CAT_CALL_INVITATION" public let ntfCategoryConnectionEvent = "NTF_CAT_CONNECTION_EVENT" +public let ntfCategoryManyEvents = "NTF_CAT_MANY_EVENTS" public let ntfCategoryCheckMessage = "NTF_CAT_CHECK_MESSAGE" public let appNotificationId = "chat.simplex.app.notification" let contactHidden = NSLocalizedString("Contact hidden:", comment: "notification") -public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest) -> UNMutableNotificationContent { +public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest, _ badgeCount: Int) -> UNMutableNotificationContent { let hideContent = ntfPreviewModeGroupDefault.get() == .hidden return createNotification( categoryIdentifier: ntfCategoryContactRequest, @@ -34,11 +35,12 @@ public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: User hideContent ? NSLocalizedString("this contact", comment: "notification title") : contactRequest.chatViewName ), targetContentIdentifier: nil, - userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId] + userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId], + badgeCount: badgeCount ) } -public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact) -> UNMutableNotificationContent { +public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact, _ badgeCount: Int) -> UNMutableNotificationContent { let hideContent = ntfPreviewModeGroupDefault.get() == .hidden return createNotification( categoryIdentifier: ntfCategoryContactConnected, @@ -51,12 +53,13 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact) hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName ), targetContentIdentifier: contact.id, - userInfo: ["userId": user.userId] + userInfo: ["userId": user.userId], // userInfo: ["chatId": contact.id, "contactId": contact.apiId] + badgeCount: badgeCount ) } -public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent { +public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent { let previewMode = ntfPreviewModeGroupDefault.get() var title: String if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir { @@ -69,12 +72,13 @@ public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ title: title, body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"), targetContentIdentifier: cInfo.id, - userInfo: ["userId": user.userId] + userInfo: ["userId": user.userId], // userInfo: ["chatId": cInfo.id, "chatItemId": cItem.id] + badgeCount: badgeCount ) } -public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutableNotificationContent { +public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCount: Int) -> UNMutableNotificationContent { let text = invitation.callType.media == .video ? NSLocalizedString("Incoming video call", comment: "notification") : NSLocalizedString("Incoming audio call", comment: "notification") @@ -84,17 +88,18 @@ public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutabl title: hideContent ? contactHidden : "\(invitation.contact.chatViewName):", body: text, targetContentIdentifier: nil, - userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId] + userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId], + badgeCount: badgeCount ) } -public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity) -> UNMutableNotificationContent { +public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity, _ badgeCount: Int) -> UNMutableNotificationContent { let hideContent = ntfPreviewModeGroupDefault.get() == .hidden var title: String var body: String? = nil var targetContentIdentifier: String? = nil switch connEntity { - case let .rcvDirectMsgConnection(contact): + case let .rcvDirectMsgConnection(_, contact): if let contact = contact { title = hideContent ? contactHidden : "\(contact.chatViewName):" targetContentIdentifier = contact.id @@ -102,7 +107,7 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit title = NSLocalizedString("New contact:", comment: "notification") } body = NSLocalizedString("message received", comment: "notification") - case let .rcvGroupMsgConnection(groupInfo, groupMember): + case let .rcvGroupMsgConnection(_, groupInfo, groupMember): title = groupMsgNtfTitle(groupInfo, groupMember, hideContent: hideContent) body = NSLocalizedString("message received", comment: "notification") targetContentIdentifier = groupInfo.id @@ -118,11 +123,12 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit title: title, body: body, targetContentIdentifier: targetContentIdentifier, - userInfo: ["userId": user.userId] + userInfo: ["userId": user.userId], + badgeCount: badgeCount ) } -public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificationContent { +public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) -> UNMutableNotificationContent { var title: String switch dbStatus { case .errorNotADatabase: @@ -142,14 +148,16 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificati } return createNotification( categoryIdentifier: ntfCategoryConnectionEvent, - title: title + title: title, + badgeCount: badgeCount ) } -public func createAppStoppedNtf() -> UNMutableNotificationContent { +public func createAppStoppedNtf(_ badgeCount: Int) -> UNMutableNotificationContent { return createNotification( categoryIdentifier: ntfCategoryConnectionEvent, - title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification") + title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification"), + badgeCount: badgeCount ) } @@ -159,8 +167,15 @@ private func groupMsgNtfTitle(_ groupInfo: GroupInfo, _ groupMember: GroupMember : "#\(groupInfo.displayName) \(groupMember.chatViewName):" } -public func createNotification(categoryIdentifier: String, title: String, subtitle: String? = nil, body: String? = nil, - targetContentIdentifier: String? = nil, userInfo: [AnyHashable : Any] = [:]) -> UNMutableNotificationContent { +public func createNotification( + categoryIdentifier: String, + title: String, + subtitle: String? = nil, + body: String? = nil, + targetContentIdentifier: String? = nil, + userInfo: [AnyHashable : Any] = [:], + badgeCount: Int +) -> UNMutableNotificationContent { let content = UNMutableNotificationContent() content.categoryIdentifier = categoryIdentifier content.title = title @@ -170,6 +185,7 @@ public func createNotification(categoryIdentifier: String, title: String, subtit content.userInfo = userInfo // TODO move logic of adding sound here, so it applies to background notifications too content.sound = .default + content.badge = badgeCount as NSNumber // content.interruptionLevel = .active // content.relevanceScore = 0.5 // 0-1 return content @@ -187,6 +203,11 @@ func hideSecrets(_ cItem: ChatItem) -> String { } return res } else { - return cItem.text + let mc = cItem.content.msgContent + if case let .report(text, reason) = mc { + return String.localizedStringWithFormat(NSLocalizedString("Report: %@", comment: "report in notification"), text.isEmpty ? reason.text : text) + } else { + return cItem.text + } } } diff --git a/apps/ios/SimpleXChat/SimpleX.h b/apps/ios/SimpleXChat/SimpleX.h index 153365424e..92dfafca21 100644 --- a/apps/ios/SimpleXChat/SimpleX.h +++ b/apps/ios/SimpleXChat/SimpleX.h @@ -10,6 +10,7 @@ #define SimpleX_h #include "hs_init.h" +#include "objc.h" extern void hs_init(int argc, char **argv[]); diff --git a/apps/ios/SimpleXChat/Theme/Color.swift b/apps/ios/SimpleXChat/Theme/Color.swift index 3e8fe1b6e7..f307eaa5aa 100644 --- a/apps/ios/SimpleXChat/Theme/Color.swift +++ b/apps/ios/SimpleXChat/Theme/Color.swift @@ -63,6 +63,23 @@ extension Color { ) } + public func toHTMLHex() -> String { + let uiColor: UIColor = .init(self) + var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) + uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) + // Can be negative values and more than 1. Extended color range, making it normal + r = min(1, max(0, r)) + g = min(1, max(0, g)) + b = min(1, max(0, b)) + a = min(1, max(0, a)) + return String(format: "#%02x%02x%02x%02x", + Int((r * 255).rounded()), + Int((g * 255).rounded()), + Int((b * 255).rounded()), + Int((a * 255).rounded()) + ) + } + public func darker(_ factor: CGFloat = 0.1) -> Color { var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) diff --git a/apps/ios/SimpleXChat/dummy.m b/apps/ios/SimpleXChat/dummy.m index 64fbc32dd3..d26e108520 100644 --- a/apps/ios/SimpleXChat/dummy.m +++ b/apps/ios/SimpleXChat/dummy.m @@ -21,4 +21,13 @@ DIR *opendir$INODE64(const char *name) { return opendir(name); } +int readdir$INODE64(DIR *restrict dirp, struct dirent *restrict entry, + struct dirent **restrict result) { + return readdir_r(dirp, entry, result); +} + +DIR *fdopendir$INODE64(const char *name) { + return opendir(name); +} + #endif diff --git a/apps/ios/SimpleXChat/hs_init.c b/apps/ios/SimpleXChat/hs_init.c index 4731e7b829..e75173d6cf 100644 --- a/apps/ios/SimpleXChat/hs_init.c +++ b/apps/ios/SimpleXChat/hs_init.c @@ -29,10 +29,10 @@ void haskell_init_nse(void) { char *argv[] = { "simplex", "+RTS", // requires `hs_init_with_rtsopts` - "-A1m", // chunk size for new allocations - "-H1m", // initial heap size + "-A256k", // chunk size for new allocations + "-H512k", // initial heap size "-F0.5", // heap growth triggering GC - "-Fd1", // memory return + "-Fd0.3", // memory return "-c", // compacting garbage collector 0 }; diff --git a/apps/ios/SimpleXChat/objc.h b/apps/ios/SimpleXChat/objc.h new file mode 100644 index 0000000000..a75a6dc5e4 --- /dev/null +++ b/apps/ios/SimpleXChat/objc.h @@ -0,0 +1,20 @@ +// +// objc.h +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 09.09.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +#ifndef objc_h +#define objc_h + +#import + +@interface ObjC : NSObject + ++ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error; + +@end + +#endif /* objc_h */ diff --git a/apps/ios/SimpleXChat/objc.m b/apps/ios/SimpleXChat/objc.m new file mode 100644 index 0000000000..c6952578ab --- /dev/null +++ b/apps/ios/SimpleXChat/objc.m @@ -0,0 +1,25 @@ +// +// objc.m +// SimpleXChat +// +// Created by Stanislav Dmitrenko on 09.09.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +#import "objc.h" + +@implementation ObjC + +// https://stackoverflow.com/a/36454808 ++ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { + @try { + tryBlock(); + return YES; + } + @catch (NSException *exception) { + *error = [[NSError alloc] initWithDomain: exception.name code: 0 userInfo: exception.userInfo]; + return NO; + } +} + +@end diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index aa43902c81..e4bc8f2150 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (може да се копира)"; @@ -31,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- гласови съобщения до 5 минути.\n- персонализирано време за изчезване.\n- история на редактиране."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 цветно!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(ново)"; /* No comment provided by engineer. */ "(this device v%@)" = "(това устройство v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Допринеси](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -65,10 +35,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Звезда в GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Добави контакт**: за създаване на нов линк или свързване чрез получен линк за връзка."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Добави нов контакт**: за да създадете своя еднократен QR код или линк за вашия контакт."; +"**Create 1-time link**: to create and share a new invitation link." = "**Добави контакт**: за създаване на нов линк."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Създай група**: за създаване на нова група."; @@ -80,10 +47,10 @@ "**e2e encrypted** video call" = "**e2e криптирано** видео разговор"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Моля, обърнете внимание**: използването на една и съща база данни на две устройства ще наруши декриптирането на съобщенията от вашите връзки като защита на сигурността."; @@ -92,7 +59,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: Незабавните push известия изискват парола, запазена в Keychain."; @@ -160,6 +127,9 @@ /* notification title */ "%@ wants to connect!" = "%@ иска да се свърже!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ и %lld членове"; @@ -226,9 +196,6 @@ /* No comment provided by engineer. */ "%lld new interface languages" = "%lld нови езици на интерфейса"; -/* No comment provided by engineer. */ -"%lld second(s)" = "%lld секунда(и)"; - /* No comment provided by engineer. */ "%lld seconds" = "%lld секунди"; @@ -274,7 +241,8 @@ /* No comment provided by engineer. */ "0s" = "0s"; -/* time interval */ +/* delete after time +time interval */ "1 day" = "1 ден"; /* time interval */ @@ -283,12 +251,17 @@ /* No comment provided by engineer. */ "1 minute" = "1 минута"; -/* time interval */ +/* delete after time +time interval */ "1 month" = "1 месец"; -/* time interval */ +/* delete after time +time interval */ "1 week" = "1 седмица"; +/* No comment provided by engineer. */ +"1-time link" = "Еднократен линк"; + /* No comment provided by engineer. */ "5 minutes" = "5 минути"; @@ -323,10 +296,7 @@ "Abort changing address?" = "Откажи смяна на адрес?"; /* No comment provided by engineer. */ -"About SimpleX" = "За SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Повече за SimpleX адреса"; +"About operators" = "За операторите"; /* No comment provided by engineer. */ "About SimpleX Chat" = "За SimpleX Chat"; @@ -334,11 +304,17 @@ /* No comment provided by engineer. */ "above, then choose:" = "по-горе, след това избери:"; +/* No comment provided by engineer. */ +"Accent" = "Акцент"; + /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Приеми"; +/* No comment provided by engineer. */ +"Accept conditions" = "Приеми условията"; + /* No comment provided by engineer. */ "Accept connection request?" = "Приемане на заявка за връзка?"; @@ -346,20 +322,29 @@ "Accept contact request from %@?" = "Приемане на заявка за контакт от %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Приеми инкогнито"; /* call status */ "accepted call" = "обаждането прието"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Приети условия"; + +/* No comment provided by engineer. */ +"Acknowledged" = "Потвърден"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "Грешки при потвърждението"; + +/* 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 contact" = "Добави контакт"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Добави предварително зададени сървъри"; +"Add friends" = "Добави приятели"; /* No comment provided by engineer. */ "Add profile" = "Добави профил"; @@ -370,18 +355,45 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Добави сървъри чрез сканиране на QR кодове."; +/* No comment provided by engineer. */ +"Add team members" = "Добави членове на екипа"; + /* No comment provided by engineer. */ "Add to another device" = "Добави към друго устройство"; /* No comment provided by engineer. */ "Add welcome message" = "Добави съобщение при посрещане"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Добавете членовете на вашия екип към разговорите."; + +/* No comment provided by engineer. */ +"Added media & file servers" = "Добавени медийни и файлови сървъри"; + +/* No comment provided by engineer. */ +"Added message servers" = "Добавени сървъри за съобщения"; + +/* No comment provided by engineer. */ +"Additional accent" = "Допълнителен акцент"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "Допълнителен акцент 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "Допълнителен вторичен"; + /* No comment provided by engineer. */ "Address" = "Адрес"; /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Промяната на адреса ще бъде прекъсната. Ще се използва старият адрес за получаване."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Адрес или еднократен линк?"; + +/* No comment provided by engineer. */ +"Address settings" = "Настройки на адреса"; + /* member role */ "admin" = "админ"; @@ -397,6 +409,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "Разширени мрежови настройки"; +/* No comment provided by engineer. */ +"Advanced settings" = "Разширени настройки"; + /* chat item text */ "agreeing encryption for %@…" = "съгласуване на криптиране за %@…"; @@ -412,6 +427,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "Всички данни се изтриват при въвеждане."; +/* No comment provided by engineer. */ +"All data is kept private on your device." = "Всички данни се съхраняват поверително на вашето устройство."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Всички членове на групата ще останат свързани."; @@ -427,6 +445,9 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Всички нови съобщения от %@ ще бъдат скрити!"; +/* profile dropdown */ +"All profiles" = "Всички профили"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Всички ваши контакти ще останат свързани."; @@ -442,9 +463,15 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "Позволи обаждания само ако вашият контакт ги разрешава."; +/* No comment provided by engineer. */ +"Allow calls?" = "Позволи обаждания?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Позволи изчезващи съобщения само ако вашият контакт ги разрешава."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Позволи понижаване"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа)"; @@ -460,6 +487,9 @@ /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Разреши изпращането на изчезващи съобщения."; +/* No comment provided by engineer. */ +"Allow sharing" = "Позволи споделяне"; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Позволи необратимо изтриване на изпратените съобщения. (24 часа)"; @@ -505,6 +535,9 @@ /* pref value */ "always" = "винаги"; +/* No comment provided by engineer. */ +"Always use private routing." = "Винаги използвай поверително рутиране."; + /* No comment provided by engineer. */ "Always use relay" = "Винаги използвай реле"; @@ -517,6 +550,9 @@ /* No comment provided by engineer. */ "Answer call" = "Отговор на повикване"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; + /* No comment provided by engineer. */ "App build: %@" = "Компилация на приложението: %@"; @@ -535,6 +571,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Кода за достъп до приложение се заменя с код за самоунищожение."; +/* No comment provided by engineer. */ +"App session" = "Сесия на приложението"; + /* No comment provided by engineer. */ "App version" = "Версия на приложението"; @@ -547,9 +586,18 @@ /* No comment provided by engineer. */ "Apply" = "Приложи"; +/* No comment provided by engineer. */ +"Apply to" = "Приложи към"; + /* No comment provided by engineer. */ "Archive and upload" = "Архивиране и качване"; +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "Архивирайте контактите, за да разговаряте по-късно."; + +/* No comment provided by engineer. */ +"Archived contacts" = "Архивирани контакти"; + /* No comment provided by engineer. */ "Archiving database" = "Архивиране на база данни"; @@ -595,9 +643,15 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Автоматично приемане на изображения"; +/* alert title */ +"Auto-accept settings" = "Автоматично приемане на настройки"; + /* No comment provided by engineer. */ "Back" = "Назад"; +/* No comment provided by engineer. */ +"Background" = "Фон"; + /* No comment provided by engineer. */ "Bad desktop address" = "Грешен адрес на настолното устройство"; @@ -613,12 +667,33 @@ /* No comment provided by engineer. */ "Bad message ID" = "Лошо ID на съобщението"; +/* No comment provided by engineer. */ +"Better calls" = "По-добри обаждания"; + /* No comment provided by engineer. */ "Better groups" = "По-добри групи"; +/* No comment provided by engineer. */ +"Better message dates." = "По-добри дати на съобщението."; + /* No comment provided by engineer. */ "Better messages" = "По-добри съобщения"; +/* No comment provided by engineer. */ +"Better networking" = "Подобрена мрежа"; + +/* No comment provided by engineer. */ +"Better notifications" = "Подобрени известия"; + +/* No comment provided by engineer. */ +"Better security ✅" = "По-добра сигурност ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Подобрен интерфейс"; + +/* No comment provided by engineer. */ +"Black" = "Черна"; + /* No comment provided by engineer. */ "Block" = "Блокирай"; @@ -643,12 +718,19 @@ /* rcv group event chat item */ "blocked %@" = "блокиран %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "блокиран от админ"; /* No comment provided by engineer. */ "Blocked by admin" = "Блокиран от админ"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "Размазване за по-добра поверителност."; + +/* No comment provided by engineer. */ +"Blur media" = "Размазване на медия"; + /* No comment provided by engineer. */ "bold" = "удебелен"; @@ -670,6 +752,12 @@ /* 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. */ +"Business address" = "Бизнес адрес"; + +/* No comment provided by engineer. */ +"Business chats" = "Бизнес чатове"; + /* 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) (БЕТА)."; @@ -688,16 +776,26 @@ /* No comment provided by engineer. */ "Calls" = "Обаждания"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "Обажданията са забранени!"; + /* No comment provided by engineer. */ "Camera not available" = "Камерата е неодстъпна"; +/* No comment provided by engineer. */ +"Can't call contact" = "Обаждането на контакта не е позволено"; + +/* No comment provided by engineer. */ +"Can't call member" = "Обаждането на члена не е позволено"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Не може да покани контакта!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Не може да поканят контактите!"; -/* No comment provided by engineer. */ +/* alert action +alert button */ "Cancel" = "Отказ"; /* No comment provided by engineer. */ @@ -709,15 +807,21 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Няма достъп до Keychain за запазване на паролата за базата данни"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Файлът не може да бъде получен"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Капацитетът е надвишен - получателят не е получил предишно изпратените съобщения."; + /* No comment provided by engineer. */ "Cellular" = "Мобилна мрежа"; /* No comment provided by engineer. */ "Change" = "Промени"; +/* authentication reason */ +"Change chat profiles" = "Промени чат профилите"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Промяна на паролата на базата данни?"; @@ -743,7 +847,7 @@ "Change self-destruct mode" = "Промени режима на самоунищожение"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Промени кода за достъп за самоунищожение"; /* chat item text */ @@ -761,20 +865,17 @@ /* chat item text */ "changing address…" = "промяна на адреса…"; -/* No comment provided by engineer. */ -"Chat archive" = "Архив на чата"; - /* No comment provided by engineer. */ "Chat console" = "Конзола"; /* No comment provided by engineer. */ -"Chat database" = "База данни за чата"; +"Chat database" = "База данни"; /* No comment provided by engineer. */ "Chat database deleted" = "Базата данни на чата е изтрита"; /* No comment provided by engineer. */ -"Chat database imported" = "Базата данни на чат е импортирана"; +"Chat database imported" = "Базата данни на е импортирана"; /* No comment provided by engineer. */ "Chat is running" = "Чатът работи"; @@ -792,9 +893,12 @@ "Chat preferences" = "Чат настройки"; /* No comment provided by engineer. */ -"Chats" = "Чатове"; +"Chat profile" = "Потребителски профил"; /* No comment provided by engineer. */ +"Chats" = "Чатове"; + +/* alert title */ "Check server address and try again." = "Проверете адреса на сървъра и опитайте отново."; /* No comment provided by engineer. */ @@ -938,7 +1042,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Свързване с настолно устройство"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "свързване…"; /* No comment provided by engineer. */ @@ -1011,19 +1115,19 @@ "Correct name to %@?" = "Поправи име на %@?"; /* No comment provided by engineer. */ -"Create" = "Създай"; +"Create" = "Създаване"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "Създай група с автоматично генериран профилл."; +"Create 1-time link" = "Създаване на еднократна препратка"; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Създайте адрес, за да позволите на хората да се свързват с вас."; +"Create a group using a random profile." = "Създаване група с автоматично създаден профил."; /* server test step */ -"Create file" = "Създай файл"; +"Create file" = "Създаване на файл"; /* No comment provided by engineer. */ -"Create group" = "Създай група"; +"Create group" = "Създаване на група"; /* No comment provided by engineer. */ "Create group link" = "Създай групов линк"; @@ -1044,7 +1148,7 @@ "Create secret group" = "Създай тайна група"; /* No comment provided by engineer. */ -"Create SimpleX address" = "Създай SimpleX адрес"; +"Create SimpleX address" = "Създаване на адрес в SimpleX"; /* No comment provided by engineer. */ "Create your profile" = "Създай своя профил"; @@ -1055,9 +1159,6 @@ /* copied message info */ "Created at: %@" = "Създаден на: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Създаден на %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Създаване на архивен линк"; @@ -1151,7 +1252,8 @@ /* message decrypt error item */ "Decryption error" = "Грешка при декриптиране"; -/* pref value */ +/* delete after time +pref value */ "default (%@)" = "по подразбиране (%@)"; /* No comment provided by engineer. */ @@ -1160,8 +1262,8 @@ /* No comment provided by engineer. */ "default (yes)" = "по подразбиране (да)"; -/* chat item action - swipe action */ +/* alert action +swipe action */ "Delete" = "Изтрий"; /* No comment provided by engineer. */ @@ -1182,12 +1284,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Изтрий и уведоми контакт"; -/* No comment provided by engineer. */ -"Delete archive" = "Изтрий архив"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Изтриване на архива на чата?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Изтрий чат профила"; @@ -1242,7 +1338,7 @@ /* No comment provided by engineer. */ "Delete message?" = "Изтрий съобщението?"; -/* No comment provided by engineer. */ +/* alert button */ "Delete messages" = "Изтрий съобщенията"; /* No comment provided by engineer. */ @@ -1279,7 +1375,7 @@ "deleted contact" = "изтрит контакт"; /* rcv group event chat item */ -"deleted group" = "групата изтрита"; +"deleted group" = "групата е изтрита"; /* No comment provided by engineer. */ "Delivery" = "Доставка"; @@ -1330,7 +1426,7 @@ "Direct messages" = "Лични съобщения"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Личните съобщения между членовете са забранени в тази група."; +"Direct messages between members are prohibited." = "Личните съобщения между членовете са забранени в тази група."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Деактивиране (запазване на промените)"; @@ -1354,7 +1450,7 @@ "Disappearing messages are prohibited in this chat." = "Изчезващите съобщения са забранени в този чат."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Изчезващите съобщения са забранени в тази група."; +"Disappearing messages are prohibited." = "Изчезващите съобщения са забранени в тази група."; /* No comment provided by engineer. */ "Disappears at" = "Изчезва в"; @@ -1395,7 +1491,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Понижи версията и отвори чата"; -/* chat item action */ +/* alert button +chat item action */ "Download" = "Изтегли"; /* No comment provided by engineer. */ @@ -1434,7 +1531,7 @@ /* No comment provided by engineer. */ "Enable (keep overrides)" = "Активиране (запазване на промените)"; -/* No comment provided by engineer. */ +/* alert title */ "Enable automatic message deletion?" = "Активиране на автоматично изтриване на съобщения?"; /* No comment provided by engineer. */ @@ -1599,9 +1696,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Грешка при приемане на заявка за контакт"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Грешка при достъпа до файла с базата данни"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Грешка при добавяне на член(ове)"; @@ -1636,7 +1730,7 @@ "Error decrypting file" = "Грешка при декриптирането на файла"; /* No comment provided by engineer. */ -"Error deleting chat database" = "Грешка при изтриване на чат базата данни"; +"Error deleting chat database" = "Грешка при изтриване на базата данни"; /* No comment provided by engineer. */ "Error deleting chat!" = "Грешка при изтриването на чата!"; @@ -1669,29 +1763,23 @@ "Error encrypting database" = "Грешка при криптиране на базата данни"; /* No comment provided by engineer. */ -"Error exporting chat database" = "Грешка при експортиране на чат базата данни"; +"Error exporting chat database" = "Грешка при експортиране на базата данни"; /* No comment provided by engineer. */ -"Error importing chat database" = "Грешка при импортиране на чат базата данни"; +"Error importing chat database" = "Грешка при импортиране на базата данни"; /* No comment provided by engineer. */ "Error joining group" = "Грешка при присъединяване към група"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Грешка при зареждане на %@ сървъри"; - /* No comment provided by engineer. */ "Error opening chat" = "Грешка при отваряне на чата"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Грешка при получаване на файл"; /* No comment provided by engineer. */ "Error removing member" = "Грешка при отстраняване на член"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Грешка при запазване на %@ сървъра"; - /* No comment provided by engineer. */ "Error saving group profile" = "Грешка при запазване на профила на групата"; @@ -1731,7 +1819,7 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Грешка при спиране на чата"; -/* No comment provided by engineer. */ +/* alertTitle */ "Error switching profile!" = "Грешка при смяна на профил!"; /* No comment provided by engineer. */ @@ -1758,8 +1846,9 @@ /* No comment provided by engineer. */ "Error: " = "Грешка: "; -/* file error text - snd error text */ +/* alert message +file error text +snd error text */ "Error: %@" = "Грешка: %@"; /* No comment provided by engineer. */ @@ -1771,9 +1860,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Дори когато е деактивиран в разговора."; -/* No comment provided by engineer. */ -"event happened" = "събитие се случи"; - /* No comment provided by engineer. */ "Exit without saving" = "Изход без запазване"; @@ -1826,7 +1912,7 @@ "Files and media" = "Файлове и медия"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Файловете и медията са забранени в тази група."; +"Files and media are prohibited." = "Файловете и медията са забранени в тази група."; /* No comment provided by engineer. */ "Files and media not allowed" = "Файлове и медия не са разрешени"; @@ -1897,9 +1983,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Пълно име (незадължително)"; -/* No comment provided by engineer. */ -"Full name:" = "Пълно име:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Напълно децентрализирана – видима е само за членовете."; @@ -1948,27 +2031,6 @@ /* No comment provided by engineer. */ "Group links" = "Групови линкове"; -/* No comment provided by engineer. */ -"Group members can add message reactions." = "Членовете на групата могат да добавят реакции към съобщенията."; - -/* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа)"; - -/* No comment provided by engineer. */ -"Group members can send direct messages." = "Членовете на групата могат да изпращат лични съобщения."; - -/* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Членовете на групата могат да изпращат изчезващи съобщения."; - -/* No comment provided by engineer. */ -"Group members can send files and media." = "Членовете на групата могат да изпращат файлове и медия."; - -/* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Членовете на групата могат да изпращат SimpleX линкове."; - -/* No comment provided by engineer. */ -"Group members can send voice messages." = "Членовете на групата могат да изпращат гласови съобщения."; - /* notification */ "Group message:" = "Групово съобщение:"; @@ -2029,9 +2091,6 @@ /* time unit */ "hours" = "часове"; -/* No comment provided by engineer. */ -"How it works" = "Как работи"; - /* No comment provided by engineer. */ "How SimpleX works" = "Как работи SimpleX"; @@ -2075,13 +2134,13 @@ "Immediately" = "Веднага"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Защитен от спам и злоупотреби"; +"Immune to spam" = "Защитен от спам и злоупотреби"; /* No comment provided by engineer. */ "Import" = "Импортиране"; /* No comment provided by engineer. */ -"Import chat database?" = "Импортиране на чат база данни?"; +"Import chat database?" = "Импортиране на база данни?"; /* No comment provided by engineer. */ "Import database" = "Импортиране на база данни"; @@ -2165,10 +2224,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n"; +"Instant" = "Мигновено"; /* No comment provided by engineer. */ -"Instantly" = "Мигновено"; +"Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n"; /* No comment provided by engineer. */ "Interface" = "Интерфейс"; @@ -2203,7 +2262,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Невалиден отговор"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Невалиден адрес на сървъра!"; /* item status text */ @@ -2249,7 +2308,7 @@ "Irreversible message deletion is prohibited in this chat." = "Необратимото изтриване на съобщения е забранено в този чат."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Необратимото изтриване на съобщения е забранено в тази група."; +"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." = "Позволява да имате много анонимни връзки без споделени данни между тях в един чат профил ."; @@ -2299,13 +2358,13 @@ /* No comment provided by engineer. */ "Joining group" = "Присъединяване към групата"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Запази"; /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Дръжте приложението отворено, за да го използвате от настолното устройство"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Запази неизползваната покана за връзка?"; /* No comment provided by engineer. */ @@ -2362,9 +2421,6 @@ /* No comment provided by engineer. */ "Live messages" = "Съобщения на живо"; -/* No comment provided by engineer. */ -"Local" = "Локално"; - /* No comment provided by engineer. */ "Local name" = "Локално име"; @@ -2377,24 +2433,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Режим на заключване"; -/* No comment provided by engineer. */ -"Make a private connection" = "Добави поверителна връзка"; - /* No comment provided by engineer. */ "Make one message disappear" = "Накарайте едно съобщение да изчезне"; /* No comment provided by engineer. */ "Make profile private!" = "Направи профила поверителен!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Уверете се, че %@ сървърните адреси са в правилен формат, разделени на редове и не се дублират (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Уверете се, че адресите на WebRTC ICE сървъра са в правилен формат, разделени на редове и не са дублирани."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Много хора попитаха: *ако SimpleX няма потребителски идентификатори, как може да доставя съобщения?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Маркирай като изтрито за всички"; @@ -2434,6 +2481,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Членът ще бъде премахнат от групата - това не може да бъде отменено!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Членовете на групата могат да добавят реакции към съобщенията."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Членовете на групата могат да изпращат лични съобщения."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Членовете на групата могат да изпращат изчезващи съобщения."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Членовете на групата могат да изпращат файлове и медия."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Членовете на групата могат да изпращат SimpleX линкове."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Членовете на групата могат да изпращат гласови съобщения."; + /* item status text */ "Message delivery error" = "Грешка при доставката на съобщението"; @@ -2450,7 +2518,7 @@ "Message reactions are prohibited in this chat." = "Реакциите на съобщения са забранени в този чат."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Реакциите на съобщения са забранени в тази група."; +"Message reactions are prohibited." = "Реакциите на съобщения са забранени в тази група."; /* notification */ "message received" = "получено съобщение"; @@ -2513,7 +2581,7 @@ "Migration is completed" = "Миграцията е завършена"; /* No comment provided by engineer. */ -"Migrations: %@" = "Миграции: %@"; +"Migrations:" = "Миграции:"; /* time unit */ "minutes" = "минути"; @@ -2551,7 +2619,7 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Множество профили за чат"; -/* swipe action */ +/* notification label action */ "Mute" = "Без звук"; /* No comment provided by engineer. */ @@ -2575,7 +2643,7 @@ /* No comment provided by engineer. */ "Network status" = "Състояние на мрежата"; -/* No comment provided by engineer. */ +/* delete after time */ "never" = "никога"; /* No comment provided by engineer. */ @@ -2587,9 +2655,6 @@ /* notification */ "New contact:" = "Нов контакт:"; -/* No comment provided by engineer. */ -"New database archive" = "Нов архив на база данни"; - /* No comment provided by engineer. */ "New desktop app!" = "Ново настолно приложение!"; @@ -2653,12 +2718,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Няма разрешение за запис на гласово съобщение"; +/* No comment provided by engineer. */ +"No push server" = "Локално"; + /* No comment provided by engineer. */ "No received or sent files" = "Няма получени или изпратени файлове"; /* copied message info in history */ "no text" = "няма текст"; +/* No comment provided by engineer. */ +"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; + /* No comment provided by engineer. */ "Not compatible!" = "Несъвместим!"; @@ -2675,8 +2746,8 @@ "observer" = "наблюдател"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "изключено"; /* blur media */ @@ -2688,7 +2759,7 @@ /* feature offered item */ "offered %@: %@" = "предлага %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ок"; /* No comment provided by engineer. */ @@ -2697,9 +2768,6 @@ /* No comment provided by engineer. */ "Old database" = "Стара база данни"; -/* No comment provided by engineer. */ -"Old database archive" = "Стар архив на база данни"; - /* group pref value */ "on" = "включено"; @@ -2716,7 +2784,7 @@ "Onion hosts will not be used." = "Няма се използват Onion хостове."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**."; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Само собствениците на групата могат да променят груповите настройки."; @@ -2757,7 +2825,7 @@ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Само вашият контакт може да изпраща гласови съобщения."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Отвори"; /* No comment provided by engineer. */ @@ -2775,12 +2843,6 @@ /* No comment provided by engineer. */ "Open Settings" = "Отвори настройки"; -/* authentication reason */ -"Open user profiles" = "Отвори потребителските профили"; - -/* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; - /* No comment provided by engineer. */ "Opening app…" = "Приложението се отваря…"; @@ -2842,10 +2904,7 @@ "peer-to-peer" = "peer-to-peer"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; - -/* No comment provided by engineer. */ -"Periodically" = "Периодично"; +"Periodic" = "Периодично"; /* message decrypt error item */ "Permanent decryption error" = "Постоянна грешка при декриптиране"; @@ -2910,9 +2969,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Запазете последната чернова на съобщението с прикачени файлове."; -/* No comment provided by engineer. */ -"Preset server" = "Предварително зададен сървър"; - /* No comment provided by engineer. */ "Preset server address" = "Предварително зададен адрес на сървъра"; @@ -2940,16 +2996,10 @@ /* No comment provided by engineer. */ "Profile images" = "Профилни изображения"; -/* No comment provided by engineer. */ -"Profile name" = "Име на профила"; - -/* No comment provided by engineer. */ -"Profile name:" = "Име на профила:"; - /* No comment provided by engineer. */ "Profile password" = "Профилна парола"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Актуализацията на профила ще бъде изпратена до вашите контакти."; /* No comment provided by engineer. */ @@ -3016,10 +3066,10 @@ "Read more" = "Прочетете още"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3027,9 +3077,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Прочетете повече в нашето хранилище в GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Потвърждениeто за доставка е деактивирано"; @@ -3085,7 +3132,7 @@ "Reduced battery usage" = "Намалена консумация на батерията"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Отхвърляне"; /* No comment provided by engineer. */ @@ -3211,13 +3258,14 @@ /* No comment provided by engineer. */ "Safer groups" = "По-безопасни групи"; -/* chat item action */ +/* alert button +chat item action */ "Save" = "Запази"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Запази (и уведоми контактите)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Запази и уведоми контакта"; /* No comment provided by engineer. */ @@ -3226,12 +3274,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Запази и актуализирай профила на групата"; -/* No comment provided by engineer. */ -"Save archive" = "Запази архив"; - -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Запази настройките за автоматично приемане"; - /* No comment provided by engineer. */ "Save group profile" = "Запази профила на групата"; @@ -3241,7 +3283,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Запази паролата в Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Запази настройките?"; /* No comment provided by engineer. */ @@ -3250,12 +3292,9 @@ /* No comment provided by engineer. */ "Save servers" = "Запази сървърите"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Запази сървърите?"; -/* No comment provided by engineer. */ -"Save settings?" = "Запази настройките?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Запази съобщението при посрещане?"; @@ -3364,9 +3403,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Изпращай известия"; -/* No comment provided by engineer. */ -"Send notifications:" = "Изпратени известия:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Изпращайте въпроси и идеи"; @@ -3379,7 +3415,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Изпращане до последните 100 съобщения на нови членове."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Подателят отмени прехвърлянето на файла."; /* No comment provided by engineer. */ @@ -3478,7 +3514,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Променете формата на профилните изображения"; -/* chat item action */ +/* alert action +chat item action */ "Share" = "Сподели"; /* No comment provided by engineer. */ @@ -3487,7 +3524,7 @@ /* No comment provided by engineer. */ "Share address" = "Сподели адрес"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Сподели адреса с контактите?"; /* No comment provided by engineer. */ @@ -3539,7 +3576,7 @@ "SimpleX links" = "SimpleX линкове"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "SimpleX линкове са забранени в тази група."; +"SimpleX links are prohibited." = "SimpleX линкове са забранени в тази група."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX линковете не са разрешени"; @@ -3601,9 +3638,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Спри чата"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Спрете чата, за да активирате действията с базата данни"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Спрете чата, за да експортирате, импортирате или изтриете чат базата данни. Няма да можете да получавате и изпращате съобщения, докато чатът е спрян."; @@ -3619,10 +3653,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Спри изпращането на файла?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Спри споделянето"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Спри споделянето на адреса?"; /* authentication reason */ @@ -3691,7 +3725,7 @@ /* No comment provided by engineer. */ "Test servers" = "Тествай сървърите"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Тестовете са неуспешни!"; /* No comment provided by engineer. */ @@ -3703,9 +3737,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Благодарение на потребителите – допринесете през Weblate!"; -/* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате."; @@ -3727,6 +3758,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване!"; +/* No comment provided by engineer. */ +"The future of messaging" = "Ново поколение поверителни съобщения"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хешът на предишното съобщение е различен."; @@ -3739,14 +3773,11 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Съобщението ще бъде маркирано като модерирано за всички членове."; -/* No comment provided by engineer. */ -"The next generation of private messaging" = "Ново поколение поверителни съобщения"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профилът се споделя само с вашите контакти."; +"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Втората отметка, която пропуснахме! ✅"; @@ -3817,15 +3848,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "За да направите нова връзка"; -/* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "За да не се разкрива часовата зона, файловете с изображения/глас използват UTC."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "За да защитите информацията си, включете SimpleX заключване.\nЩе бъдете подканени да извършите идентификация, преди тази функция да бъде активирана."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "За да запишете гласово съобщение, моля, дайте разрешение за използване на микрофон."; @@ -3931,7 +3962,7 @@ /* authentication reason */ "Unlock app" = "Отключи приложението"; -/* swipe action */ +/* notification label action */ "Unmute" = "Уведомявай"; /* swipe action */ @@ -4003,9 +4034,6 @@ /* No comment provided by engineer. */ "Use the app while in the call." = "Използвайте приложението по време на разговора."; -/* No comment provided by engineer. */ -"User profile" = "Потребителски профил"; - /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Използват се сървърите на SimpleX Chat."; @@ -4085,7 +4113,7 @@ "Voice messages are prohibited in this chat." = "Гласовите съобщения са забранени в този чат."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Гласовите съобщения са забранени в тази група."; +"Voice messages are prohibited." = "Гласовите съобщения са забранени в тази група."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Гласовите съобщения не са разрешени"; @@ -4144,9 +4172,6 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "При свързване на аудио и видео разговори."; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когато споделяте инкогнито профил с някого, този профил ще се използва за групите, в които той ви кани."; @@ -4180,9 +4205,6 @@ /* No comment provided by engineer. */ "you" = "вие"; -/* No comment provided by engineer. */ -"You" = "Вие"; - /* No comment provided by engineer. */ "You **must not** use the same database on two devices." = "**Не трябва** да използвате една и съща база данни на две устройства."; @@ -4267,9 +4289,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Можете да споделите този адрес с вашите контакти, за да им позволите да се свържат с **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Можете да споделите адреса си като линк или QR код - всеки може да се свърже с вас."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Можете да започнете чат през Настройки на приложението / База данни или като рестартирате приложението"; @@ -4279,7 +4298,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Можете да използвате markdown за форматиране на съобщенията:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Можете да видите отново линкът за покана в подробностите за връзката."; /* No comment provided by engineer. */ @@ -4298,10 +4317,10 @@ "you changed role of %@ to %@" = "променихте ролята на %1$@ на %2$@"; /* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Вие контролирате през кой сървър(и) **да получавате** съобщенията, вашите контакти – сървърите, които използвате, за да им изпращате съобщения."; +"You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново."; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново."; +"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Вече сте заявили връзка през този адрес!"; @@ -4384,17 +4403,14 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Използвате инкогнито профил за тази група - за да се предотврати споделянето на основния ви профил, поканите на контакти не са разрешени"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Вашите %@ сървъри"; - /* No comment provided by engineer. */ "Your calls" = "Вашите обаждания"; /* No comment provided by engineer. */ -"Your chat database" = "Вашата чат база данни"; +"Your chat database" = "Вашата база данни"; /* No comment provided by engineer. */ -"Your chat database is not encrypted - set passphrase to encrypt it." = "Вашата чат база данни не е криптирана - задайте парола, за да я криптирате."; +"Your chat database is not encrypted - set passphrase to encrypt it." = "Вашата база данни не е криптирана - задайте парола, за да я криптирате."; /* No comment provided by engineer. */ "Your chat profiles" = "Вашите чат профили"; @@ -4409,7 +4425,7 @@ "Your contacts will remain connected." = "Вашите контакти ще останат свързани."; /* No comment provided by engineer. */ -"Your current chat database will be DELETED and REPLACED with the imported one." = "Вашата текуща чат база данни ще бъде ИЗТРИТА и ЗАМЕНЕНА с импортираната."; +"Your current chat database will be DELETED and REPLACED with the imported one." = "Вашата текуща база данни ще бъде ИЗТРИТА и ЗАМЕНЕНА с импортираната."; /* No comment provided by engineer. */ "Your current profile" = "Вашият текущ профил"; @@ -4430,7 +4446,7 @@ "Your profile **%@** will be shared." = "Вашият профил **%@** ще бъде споделен."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти.\nSimpleX сървърите не могат да видят вашия профил."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство."; @@ -4438,9 +4454,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Вашият автоматично генериран профил"; -/* No comment provided by engineer. */ -"Your server" = "Вашият сървър"; - /* No comment provided by engineer. */ "Your server address" = "Вашият адрес на сървъра"; @@ -4448,11 +4461,5 @@ "Your settings" = "Вашите настройки"; /* No comment provided by engineer. */ -"Your SimpleX address" = "Вашият SimpleX адрес"; - -/* No comment provided by engineer. */ -"Your SMP servers" = "Вашите SMP сървъри"; - -/* No comment provided by engineer. */ -"Your XFTP servers" = "Вашите XFTP сървъри"; +"Your SimpleX address" = "Вашият адрес в SimpleX"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 220550c682..08a94615a3 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (lze kopírovat)"; @@ -28,23 +13,11 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 minutové hlasové zprávy.\n- vlastní čas mizení.\n- historie úprav."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 barevný!"; /* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - -/* 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)"; @@ -55,9 +28,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt."; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e šifrovaný** audio hovor"; @@ -65,16 +35,16 @@ "**e2e encrypted** video call" = "**e2e šifrovaný** videohovor"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte)."; /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence."; @@ -121,6 +91,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ připojen"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ staženo"; + /* notification title */ "%@ is connected!" = "%@ je připojen!"; @@ -130,9 +103,18 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ je ověřený"; +/* No comment provided by engineer. */ +"%@ server" = "%@ server"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ servery"; + /* notification title */ "%@ wants to connect!" = "%@ se chce připojit!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld other members connected" = "%@, %@ a %lld ostatní členové připojeni"; @@ -142,6 +124,18 @@ /* time interval */ "%d days" = "%d dní"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d soubor(y) stále stahován(y)."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%d soubor(y) se nepodařilo stáhnout."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d soubor(y) smazán(y)."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d soubor(y) nestažen(y)."; + /* time interval */ "%d hours" = "%d hodin"; @@ -175,15 +169,21 @@ /* No comment provided by engineer. */ "%lld members" = "%lld členové"; +/* No comment provided by engineer. */ +"%lld messages blocked" = "%lld zprávy blokovaný"; + +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld zprávy blokovaný adminem"; + +/* No comment provided by engineer. */ +"%lld messages marked deleted" = "%lld zprávy označeno jako smazáno"; + /* No comment provided by engineer. */ "%lld minutes" = "%lld minut"; /* No comment provided by engineer. */ "%lld new interface languages" = "%d nové jazyky rozhraní"; -/* No comment provided by engineer. */ -"%lld second(s)" = "%lld vteřin"; - /* No comment provided by engineer. */ "%lld seconds" = "%lld vteřin"; @@ -226,7 +226,8 @@ /* No comment provided by engineer. */ "0s" = "0s"; -/* time interval */ +/* delete after time +time interval */ "1 day" = "1 den"; /* time interval */ @@ -235,10 +236,12 @@ /* No comment provided by engineer. */ "1 minute" = "1 minutu"; -/* time interval */ +/* delete after time +time interval */ "1 month" = "1 měsíc"; -/* time interval */ +/* delete after time +time interval */ "1 week" = "1 týden"; /* No comment provided by engineer. */ @@ -274,12 +277,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Přerušit změnu adresy?"; -/* No comment provided by engineer. */ -"About SimpleX" = "O SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "O SimpleX adrese"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "O SimpleX chat"; @@ -287,8 +284,8 @@ "above, then choose:" = "výše, pak vyberte:"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Přijmout"; /* No comment provided by engineer. */ @@ -298,7 +295,7 @@ "Accept contact request from %@?" = "Přijmout žádost o kontakt od %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Přijmout inkognito"; /* call status */ @@ -307,9 +304,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům."; -/* No comment provided by engineer. */ -"Add preset servers" = "Přidejte přednastavené servery"; - /* No comment provided by engineer. */ "Add profile" = "Přidat profil"; @@ -436,6 +430,9 @@ /* No comment provided by engineer. */ "Answer call" = "Přijmout hovor"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Servery může provozovat kdokoli."; + /* No comment provided by engineer. */ "App build: %@" = "Sestavení aplikace: %@"; @@ -562,7 +559,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Nelze pozvat kontakty!"; -/* No comment provided by engineer. */ +/* alert action +alert button */ "Cancel" = "Zrušit"; /* feature offered item */ @@ -571,7 +569,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Nelze získat přístup ke klíčence pro uložení hesla databáze"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nelze přijmout soubor"; /* No comment provided by engineer. */ @@ -602,7 +600,7 @@ "Change self-destruct mode" = "Změnit režim sebedestrukce"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Změnit sebedestrukční heslo"; /* chat item text */ @@ -620,9 +618,6 @@ /* chat item text */ "changing address…" = "změna adresy…"; -/* No comment provided by engineer. */ -"Chat archive" = "Chat se archivuje"; - /* No comment provided by engineer. */ "Chat console" = "Konzola pro chat"; @@ -645,9 +640,12 @@ "Chat preferences" = "Předvolby chatu"; /* No comment provided by engineer. */ -"Chats" = "Chaty"; +"Chat profile" = "Profil uživatele"; /* No comment provided by engineer. */ +"Chats" = "Chaty"; + +/* alert title */ "Check server address and try again." = "Zkontrolujte adresu serveru a zkuste to znovu."; /* No comment provided by engineer. */ @@ -746,7 +744,7 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Připojování k serveru... (chyba: %@)"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "připojení…"; /* No comment provided by engineer. */ @@ -812,9 +810,6 @@ /* No comment provided by engineer. */ "Create" = "Vytvořit"; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Vytvořit adresu, aby se s vámi lidé mohli spojit."; - /* server test step */ "Create file" = "Vytvořit soubor"; @@ -827,6 +822,9 @@ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻"; +/* No comment provided by engineer. */ +"Create profile" = "Vytvořte si profil"; + /* server test step */ "Create queue" = "Vytvořit frontu"; @@ -839,9 +837,6 @@ /* No comment provided by engineer. */ "Create your profile" = "Vytvořte si profil"; -/* No comment provided by engineer. */ -"Created on %@" = "Vytvořeno na %@"; - /* No comment provided by engineer. */ "creator" = "tvůrce"; @@ -929,7 +924,8 @@ /* message decrypt error item */ "Decryption error" = "Chyba dešifrování"; -/* pref value */ +/* delete after time +pref value */ "default (%@)" = "výchozí (%@)"; /* No comment provided by engineer. */ @@ -938,8 +934,8 @@ /* No comment provided by engineer. */ "default (yes)" = "výchozí (ano)"; -/* chat item action - swipe action */ +/* alert action +swipe action */ "Delete" = "Smazat"; /* No comment provided by engineer. */ @@ -954,12 +950,6 @@ /* No comment provided by engineer. */ "Delete all files" = "Odstranit všechny soubory"; -/* No comment provided by engineer. */ -"Delete archive" = "Smazat archiv"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Smazat archiv chatu?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Smazat chat profil"; @@ -1011,7 +1001,7 @@ /* No comment provided by engineer. */ "Delete message?" = "Smazat zprávu?"; -/* No comment provided by engineer. */ +/* alert button */ "Delete messages" = "Smazat zprávy"; /* No comment provided by engineer. */ @@ -1087,7 +1077,7 @@ "Direct messages" = "Přímé zprávy"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Přímé zprávy mezi členy jsou v této skupině zakázány."; +"Direct messages between members are prohibited." = "Přímé zprávy mezi členy jsou v této skupině zakázány."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Vypnout (zachovat přepsání)"; @@ -1111,7 +1101,7 @@ "Disappearing messages are prohibited in this chat." = "Mizící zprávy jsou v tomto chatu zakázány."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Mizící zprávy jsou v této skupině zakázány."; +"Disappearing messages are prohibited." = "Mizící zprávy jsou v této skupině zakázány."; /* No comment provided by engineer. */ "Disappears at" = "Zmizí v"; @@ -1170,7 +1160,7 @@ /* No comment provided by engineer. */ "Enable (keep overrides)" = "Povolit (zachovat přepsání)"; -/* No comment provided by engineer. */ +/* alert title */ "Enable automatic message deletion?" = "Povolit automatické mazání zpráv?"; /* No comment provided by engineer. */ @@ -1305,9 +1295,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Chyba při přijímání žádosti o kontakt"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Chyba přístupu k souboru databáze"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Chyba přidávání člena(ů)"; @@ -1377,18 +1364,12 @@ /* No comment provided by engineer. */ "Error joining group" = "Chyba při připojování ke skupině"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Chyba načítání %@ serverů"; - -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Chyba při příjmu souboru"; /* No comment provided by engineer. */ "Error removing member" = "Chyba při odebrání člena"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Chyba při ukládání serverů %@"; - /* No comment provided by engineer. */ "Error saving group profile" = "Chyba při ukládání profilu skupiny"; @@ -1422,7 +1403,7 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Chyba při zastavení chatu"; -/* No comment provided by engineer. */ +/* alertTitle */ "Error switching profile!" = "Chyba při přepínání profilu!"; /* No comment provided by engineer. */ @@ -1443,8 +1424,9 @@ /* No comment provided by engineer. */ "Error: " = "Chyba: "; -/* file error text - snd error text */ +/* alert message +file error text +snd error text */ "Error: %@" = "Chyba: %@"; /* No comment provided by engineer. */ @@ -1499,7 +1481,7 @@ "Files and media" = "Soubory a média"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Soubory a média jsou zakázány v této skupině."; +"Files and media are prohibited." = "Soubory a média jsou zakázány v této skupině."; /* No comment provided by engineer. */ "Files and media prohibited!" = "Soubory a média jsou zakázány!"; @@ -1543,9 +1525,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Celé jméno (volitelně)"; -/* No comment provided by engineer. */ -"Full name:" = "Celé jméno:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Plně přepracováno, prácuje na pozadí!"; @@ -1585,24 +1564,6 @@ /* No comment provided by engineer. */ "Group links" = "Odkazy na skupiny"; -/* No comment provided by engineer. */ -"Group members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy."; - -/* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)"; - -/* No comment provided by engineer. */ -"Group members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy."; - -/* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy."; - -/* No comment provided by engineer. */ -"Group members can send files and media." = "Členové skupiny mohou posílat soubory a média."; - -/* No comment provided by engineer. */ -"Group members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy."; - /* notification */ "Group message:" = "Skupinová zpráva:"; @@ -1660,9 +1621,6 @@ /* time unit */ "hours" = "hodin"; -/* No comment provided by engineer. */ -"How it works" = "Jak to funguje"; - /* No comment provided by engineer. */ "How SimpleX works" = "Jak SimpleX funguje"; @@ -1703,7 +1661,7 @@ "Immediately" = "Ihned"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Odolná vůči spamu a zneužití"; +"Immune to spam" = "Odolná vůči spamu a zneužití"; /* No comment provided by engineer. */ "Import" = "Import"; @@ -1772,10 +1730,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n"; +"Instant" = "Okamžitě"; /* No comment provided by engineer. */ -"Instantly" = "Okamžitě"; +"Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n"; /* No comment provided by engineer. */ "Interface" = "Rozhranní"; @@ -1792,7 +1750,7 @@ /* invalid chat item */ "invalid data" = "neplatné údaje"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Neplatná adresa serveru!"; /* item status text */ @@ -1838,7 +1796,7 @@ "Irreversible message deletion is prohibited in this chat." = "Nevratné mazání zpráv je v tomto chatu zakázáno."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Nevratné mazání zpráv je v této skupině zakázáno."; +"Irreversible message deletion is prohibited." = "Nevratné mazání zpráv je v této skupině zakázáno."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Umožňuje mít v jednom profilu chatu mnoho anonymních spojení bez jakýchkoli sdílených údajů mezi nimi."; @@ -1921,9 +1879,6 @@ /* No comment provided by engineer. */ "Live messages" = "Živé zprávy"; -/* No comment provided by engineer. */ -"Local" = "Místní"; - /* No comment provided by engineer. */ "Local name" = "Místní název"; @@ -1936,24 +1891,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Režim zámku"; -/* No comment provided by engineer. */ -"Make a private connection" = "Vytvořte si soukromé připojení"; - /* No comment provided by engineer. */ "Make one message disappear" = "Nechat jednu zprávu zmizet"; /* No comment provided by engineer. */ "Make profile private!" = "Změnit profil na soukromý!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Ujistěte se, že adresy %@ serverů jsou ve správném formátu, oddělené řádky a nejsou duplicitní (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Mnoho lidí se ptalo: *Pokud SimpleX nemá žádné uživatelské identifikátory, jak může doručovat zprávy?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Označit jako smazané pro všechny"; @@ -1990,6 +1936,24 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Člen bude odstraněn ze skupiny - toto nelze vzít zpět!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Členové skupiny mohou posílat soubory a média."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy."; + /* item status text */ "Message delivery error" = "Chyba doručení zprávy"; @@ -2006,7 +1970,7 @@ "Message reactions are prohibited in this chat." = "Reakce na zprávy jsou v tomto chatu zakázány."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Reakce na zprávy jsou v této skupině zakázány."; +"Message reactions are prohibited." = "Reakce na zprávy jsou v této skupině zakázány."; /* notification */ "message received" = "zpráva přijata"; @@ -2033,7 +1997,7 @@ "Migration is completed" = "Přenesení dokončeno"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrace: %@"; +"Migrations:" = "Migrace:"; /* time unit */ "minutes" = "minut"; @@ -2068,7 +2032,7 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Více chatovacích profilů"; -/* swipe action */ +/* notification label action */ "Mute" = "Ztlumit"; /* No comment provided by engineer. */ @@ -2086,7 +2050,7 @@ /* No comment provided by engineer. */ "Network status" = "Stav sítě"; -/* No comment provided by engineer. */ +/* delete after time */ "never" = "nikdy"; /* notification */ @@ -2095,9 +2059,6 @@ /* notification */ "New contact:" = "Nový kontakt:"; -/* No comment provided by engineer. */ -"New database archive" = "Archiv nové databáze"; - /* No comment provided by engineer. */ "New desktop app!" = "Nová desktopová aplikace!"; @@ -2158,12 +2119,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Nemáte oprávnění nahrávat hlasové zprávy"; +/* No comment provided by engineer. */ +"No push server" = "Místní"; + /* No comment provided by engineer. */ "No received or sent files" = "Žádné přijaté ani odeslané soubory"; /* copied message info in history */ "no text" = "žádný text"; +/* No comment provided by engineer. */ +"No user identifiers." = "Bez uživatelských identifikátorů"; + /* No comment provided by engineer. */ "Notifications" = "Oznámení"; @@ -2177,8 +2144,8 @@ "observer" = "pozorovatel"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "vypnuto"; /* blur media */ @@ -2190,15 +2157,12 @@ /* feature offered item */ "offered %@: %@" = "nabídl %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ "Old database" = "Stará databáze"; -/* No comment provided by engineer. */ -"Old database archive" = "Archiv staré databáze"; - /* group pref value */ "on" = "zapnuto"; @@ -2215,7 +2179,7 @@ "Onion hosts will not be used." = "Onion hostitelé nebudou použiti."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**."; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Předvolby skupiny mohou měnit pouze vlastníci skupiny."; @@ -2256,7 +2220,7 @@ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Hlasové zprávy může odesílat pouze váš kontakt."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Otevřít"; /* No comment provided by engineer. */ @@ -2268,12 +2232,6 @@ /* No comment provided by engineer. */ "Open Settings" = "Otevřít nastavení"; -/* authentication reason */ -"Open user profiles" = "Otevřít uživatelské profily"; - -/* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli."; - /* member role */ "owner" = "vlastník"; @@ -2302,10 +2260,7 @@ "peer-to-peer" = "peer-to-peer"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazů, které sdílíte."; - -/* No comment provided by engineer. */ -"Periodically" = "Pravidelně"; +"Periodic" = "Pravidelně"; /* message decrypt error item */ "Permanent decryption error" = "Chyba dešifrování"; @@ -2361,9 +2316,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachování posledního návrhu zprávy s přílohami."; -/* No comment provided by engineer. */ -"Preset server" = "Přednastavený server"; - /* No comment provided by engineer. */ "Preset server address" = "Přednastavená adresa serveru"; @@ -2388,7 +2340,7 @@ /* No comment provided by engineer. */ "Profile password" = "Heslo profilu"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Aktualizace profilu bude zaslána vašim kontaktům."; /* No comment provided by engineer. */ @@ -2443,7 +2395,7 @@ "Read more" = "Přečíst více"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -2451,9 +2403,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Další informace najdete v našem repozitáři GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Informace o dodání jsou zakázány"; @@ -2503,7 +2452,7 @@ "Reduced battery usage" = "Snížení spotřeby baterie"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Odmítnout"; /* No comment provided by engineer. */ @@ -2602,13 +2551,14 @@ /* No comment provided by engineer. */ "Run chat" = "Spustit chat"; -/* chat item action */ +/* alert button +chat item action */ "Save" = "Uložit"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Uložit (a informovat kontakty)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Uložit a upozornit kontakt"; /* No comment provided by engineer. */ @@ -2617,12 +2567,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Uložit a aktualizovat profil skupiny"; -/* No comment provided by engineer. */ -"Save archive" = "Uložit archiv"; - -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Uložit nastavení automatického přijímání"; - /* No comment provided by engineer. */ "Save group profile" = "Uložení profilu skupiny"; @@ -2632,7 +2576,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Uložit přístupovou frázi do Klíčenky"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Uložit předvolby?"; /* No comment provided by engineer. */ @@ -2641,12 +2585,9 @@ /* No comment provided by engineer. */ "Save servers" = "Uložit servery"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Uložit servery?"; -/* No comment provided by engineer. */ -"Save settings?" = "Uložit nastavení?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Uložit uvítací zprávu?"; @@ -2731,9 +2672,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Odeslat oznámení"; -/* No comment provided by engineer. */ -"Send notifications:" = "Odeslat oznámení:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Zasílání otázek a nápadů"; @@ -2743,7 +2681,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Odeslat je z galerie nebo vlastní klávesnice."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Odesílatel zrušil přenos souboru."; /* No comment provided by engineer. */ @@ -2827,7 +2765,8 @@ /* No comment provided by engineer. */ "Settings" = "Nastavení"; -/* chat item action */ +/* alert action +chat item action */ "Share" = "Sdílet"; /* No comment provided by engineer. */ @@ -2836,7 +2775,7 @@ /* No comment provided by engineer. */ "Share address" = "Sdílet adresu"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Sdílet adresu s kontakty?"; /* No comment provided by engineer. */ @@ -2926,9 +2865,6 @@ /* No comment provided by engineer. */ "Stop" = "Zastavit"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Zastavte chat pro povolení akcí databáze"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Zastavení chatu pro export, import nebo smazání databáze chatu. Během zastavení chatu nebudete moci přijímat a odesílat zprávy."; @@ -2944,10 +2880,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Zastavit odesílání souboru?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Přestat sdílet"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Přestat sdílet adresu?"; /* authentication reason */ @@ -3004,7 +2940,7 @@ /* No comment provided by engineer. */ "Test servers" = "Testovací servery"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testy selhaly!"; /* No comment provided by engineer. */ @@ -3016,9 +2952,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Díky uživatelům - přispívejte prostřednictvím Weblate!"; -/* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "1. Platforma bez identifikátorů uživatelů - soukromá už od záměru."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení."; @@ -3037,6 +2970,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!"; +/* No comment provided by engineer. */ +"The future of messaging" = "Nová generace soukromých zpráv"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Hash předchozí zprávy se liší."; @@ -3049,14 +2985,11 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Zpráva bude pro všechny členy označena jako moderovaná."; -/* No comment provided by engineer. */ -"The next generation of private messaging" = "Nová generace soukromých zpráv"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; +"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅"; @@ -3103,15 +3036,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "Vytvoření nového připojení"; -/* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "K ochraně časového pásma používají obrazové/hlasové soubory UTC."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Chcete-li chránit své informace, zapněte zámek SimpleX Lock.\nPřed zapnutím této funkce budete vyzváni k dokončení ověření."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Chcete-li nahrávat hlasové zprávy, udělte povolení k použití mikrofonu."; @@ -3187,7 +3120,7 @@ /* authentication reason */ "Unlock app" = "Odemknout aplikaci"; -/* swipe action */ +/* notification label action */ "Unmute" = "Zrušit ztlumení"; /* swipe action */ @@ -3238,9 +3171,6 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Používat servery SimpleX Chat?"; -/* No comment provided by engineer. */ -"User profile" = "Profil uživatele"; - /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Používat servery SimpleX Chat."; @@ -3296,7 +3226,7 @@ "Voice messages are prohibited in this chat." = "Hlasové zprávy jsou v tomto chatu zakázány."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Hlasové zprávy jsou v této skupině zakázány."; +"Voice messages are prohibited." = "Hlasové zprávy jsou v této skupině zakázány."; /* No comment provided by engineer. */ "Voice messages prohibited!" = "Hlasové zprávy jsou zakázány!"; @@ -3340,9 +3270,6 @@ /* No comment provided by engineer. */ "When available" = "Když je k dispozici"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Když někdo požádá o připojení, můžete žádost přijmout nebo odmítnout."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve."; @@ -3358,9 +3285,6 @@ /* pref value */ "yes" = "ano"; -/* No comment provided by engineer. */ -"You" = "Vy"; - /* No comment provided by engineer. */ "You accepted connection" = "Přijali jste spojení"; @@ -3412,9 +3336,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Chat můžete zahájit prostřednictvím aplikace Nastavení / Databáze nebo restartováním aplikace"; @@ -3440,10 +3361,10 @@ "you changed role of %@ to %@" = "změnili jste roli z %1$@ na %2$@"; /* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Sami řídíte, přes který server(y) **přijímat** zprávy, své kontakty – servery, které používáte k odesílání zpráv."; +"You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu."; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu."; +"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte."; /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení."; @@ -3511,9 +3432,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Pro tuto skupinu používáte inkognito profil - abyste zabránili sdílení svého hlavního profilu, není pozvání kontaktů povoleno"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Vaše servery %@"; - /* No comment provided by engineer. */ "Your calls" = "Vaše hovory"; @@ -3554,7 +3472,7 @@ "Your profile **%@** will be shared." = "Váš profil **%@** bude sdílen."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty.\nServery SimpleX nevidí váš profil."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení."; @@ -3562,9 +3480,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Váš náhodný profil"; -/* No comment provided by engineer. */ -"Your server" = "Váš server"; - /* No comment provided by engineer. */ "Your server address" = "Adresa vašeho serveru"; @@ -3574,9 +3489,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Vaše SimpleX adresa"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Vaše servery SMP"; - -/* No comment provided by engineer. */ -"Your XFTP servers" = "Vaše XFTP servery"; - diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index bab14eda6b..8da7835c43 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (kann kopiert werden)"; @@ -31,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- Bis zu 5 Minuten lange Sprachnachrichten\n- Zeitdauer für verschwindende Nachrichten anpassen\n- Nachrichtenverlauf bearbeiten"; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 farbig!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(Neu)"; /* No comment provided by engineer. */ "(this device v%@)" = "(Dieses Gerät hat v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* 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)"; @@ -65,10 +35,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen oder eine Verbindung über einen Link herzustellen, den Sie erhalten haben."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Neuen Kontakt hinzufügen**: Um einen Einmal-QR-Code oder -Link für Ihren Kontakt zu erzeugen."; +"**Create 1-time link**: to create and share a new invitation link." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Gruppe erstellen**: Um eine neue Gruppe zu erstellen."; @@ -80,10 +47,10 @@ "**e2e encrypted** video call" = "**E2E-verschlüsselter** Videoanruf"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen."; @@ -92,7 +59,10 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde."; + +/* No comment provided by engineer. */ +"**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. */ "**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."; @@ -154,12 +124,21 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ wurde erfolgreich überprüft"; +/* No comment provided by engineer. */ +"%@ server" = "%@ Server"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ Server"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ hochgeladen"; /* notification title */ "%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ und %lld Mitglieder"; @@ -172,9 +151,24 @@ /* time interval */ "%d days" = "%d Tage"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d Datei(en) wird/werden immer noch heruntergeladen."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "Bei %d Datei(en) ist das Herunterladen fehlgeschlagen."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d Datei(en) wurde(n) gelöscht."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d Datei(en) wurde(n) nicht heruntergeladen."; + /* time interval */ "%d hours" = "%d Stunden"; +/* alert title */ +"%d messages not forwarded" = "%d Nachrichten wurden nicht weitergeleitet"; + /* time interval */ "%d min" = "%d min"; @@ -184,6 +178,9 @@ /* time interval */ "%d sec" = "%d s"; +/* delete after time */ +"%d seconds(s)" = "%d Sekunde(n)"; + /* integrity error chat item */ "%d skipped message(s)" = "%d übersprungene Nachricht(en)"; @@ -226,9 +223,6 @@ /* No comment provided by engineer. */ "%lld new interface languages" = "%lld neue Sprachen für die Bedienoberfläche"; -/* No comment provided by engineer. */ -"%lld second(s)" = "%lld Sekunde(n)"; - /* No comment provided by engineer. */ "%lld seconds" = "%lld Sekunden"; @@ -274,8 +268,9 @@ /* No comment provided by engineer. */ "0s" = "0s"; -/* time interval */ -"1 day" = "täglich"; +/* delete after time +time interval */ +"1 day" = "Älter als ein Tag"; /* time interval */ "1 hour" = "1 Stunde"; @@ -283,11 +278,22 @@ /* No comment provided by engineer. */ "1 minute" = "1 Minute"; -/* time interval */ -"1 month" = "monatlich"; +/* delete after time +time interval */ +"1 month" = "Älter als ein Monat"; -/* time interval */ -"1 week" = "wöchentlich"; +/* delete after time +time interval */ +"1 week" = "Älter als eine Woche"; + +/* delete after time */ +"1 year" = "Älter als ein Jahr"; + +/* No comment provided by engineer. */ +"1-time link" = "Einmal-Link"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Ein Einmal-Link kann *nur mit einem Kontakt* genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger."; /* No comment provided by engineer. */ "5 minutes" = "5 Minuten"; @@ -323,10 +329,7 @@ "Abort changing address?" = "Wechsel der Empfängeradresse beenden?"; /* No comment provided by engineer. */ -"About SimpleX" = "Über SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Über die SimpleX-Adresse"; +"About operators" = "Über die Betreiber"; /* No comment provided by engineer. */ "About SimpleX Chat" = "Über SimpleX Chat"; @@ -338,10 +341,13 @@ "Accent" = "Akzent"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Annehmen"; +/* No comment provided by engineer. */ +"Accept conditions" = "Nutzungsbedingungen akzeptieren"; + /* No comment provided by engineer. */ "Accept connection request?" = "Kontaktanfrage annehmen?"; @@ -349,18 +355,27 @@ "Accept contact request from %@?" = "Die Kontaktanfrage von %@ annehmen?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Inkognito akzeptieren"; /* call status */ "accepted call" = "Anruf angenommen"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Akzeptierte Nutzungsbedingungen"; + +/* chat list item title */ +"accepted invitation" = "Einladung angenommen"; + /* No comment provided by engineer. */ "Acknowledged" = "Bestätigt"; /* No comment provided by engineer. */ "Acknowledgement errors" = "Fehler bei der Bestätigung"; +/* token status text */ +"Active" = "Aktiv"; + /* No comment provided by engineer. */ "Active connections" = "Aktive Verbindungen"; @@ -368,26 +383,41 @@ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; /* No comment provided by engineer. */ -"Add contact" = "Kontakt hinzufügen"; +"Add friends" = "Freunde aufnehmen"; /* No comment provided by engineer. */ -"Add preset servers" = "Füge voreingestellte Server hinzu"; +"Add list" = "Liste hinzufügen"; /* No comment provided by engineer. */ "Add profile" = "Profil hinzufügen"; /* No comment provided by engineer. */ -"Add server" = "Füge Server hinzu"; +"Add server" = "Server hinzufügen"; /* No comment provided by engineer. */ -"Add servers by scanning QR codes." = "Fügen Sie Server durch Scannen der QR Codes hinzu."; +"Add servers by scanning QR codes." = "Server durch Scannen von QR Codes hinzufügen."; + +/* No comment provided by engineer. */ +"Add team members" = "Team-Mitglieder aufnehmen"; /* No comment provided by engineer. */ "Add to another device" = "Einem anderen Gerät hinzufügen"; +/* No comment provided by engineer. */ +"Add to list" = "Zur Liste hinzufügen"; + /* No comment provided by engineer. */ "Add welcome message" = "Begrüßungsmeldung hinzufügen"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Nehmen Sie Team-Mitglieder in Ihre Unterhaltungen auf."; + +/* No comment provided by engineer. */ +"Added media & file servers" = "Medien- und Dateiserver hinzugefügt"; + +/* No comment provided by engineer. */ +"Added message servers" = "Nachrichtenserver hinzugefügt"; + /* No comment provided by engineer. */ "Additional accent" = "Erste Akzentfarbe"; @@ -403,6 +433,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Empfängeradresse wird beendet. Die bisherige Adresse wird weiter verwendet."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Adress- oder Einmal-Link?"; + +/* No comment provided by engineer. */ +"Address settings" = "Adress-Einstellungen"; + /* member role */ "admin" = "Admin"; @@ -427,17 +463,23 @@ /* chat item text */ "agreeing encryption…" = "Verschlüsselung zustimmen…"; +/* No comment provided by engineer. */ +"All" = "Alle"; + /* No comment provided by engineer. */ "All app data is deleted." = "Werden die App-Daten komplett gelöscht."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Es werden alle Chats und Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Alle Chats werden von der Liste %@ entfernt und danach wird die Liste gelöscht."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Alle Daten werden gelöscht, sobald dieser eingegeben wird."; /* No comment provided by engineer. */ -"All data is private to your device." = "Alle Daten werden nur auf Ihrem Gerät gespeichert."; +"All data is kept private on your device." = "Alle Daten werden nur auf Ihrem Gerät gespeichert."; /* No comment provided by engineer. */ "All group members will remain connected." = "Alle Gruppenmitglieder bleiben verbunden."; @@ -445,6 +487,9 @@ /* feature role */ "all members" = "Alle Mitglieder"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!"; @@ -454,9 +499,15 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Von %@ werden alle neuen Nachrichten ausgeblendet!"; -/* No comment provided by engineer. */ +/* profile dropdown */ "All profiles" = "Alle Profile"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Alle Meldungen werden für Sie archiviert."; + +/* No comment provided by engineer. */ +"All servers" = "Alle Server"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Alle Ihre Kontakte bleiben verbunden."; @@ -502,6 +553,9 @@ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Melden von Nachrichten an Moderatoren erlauben."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Das Senden von Dateien und Medien erlauben."; @@ -556,9 +610,15 @@ /* No comment provided by engineer. */ "and %lld other events" = "und %lld weitere Ereignisse"; +/* report reason */ +"Another reason" = "Anderer Grund"; + /* 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: %@"; @@ -568,6 +628,9 @@ /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt."; +/* No comment provided by engineer. */ +"App group:" = "App-Gruppe:"; + /* No comment provided by engineer. */ "App icon" = "App-Icon"; @@ -577,6 +640,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "App-Zugangscode wurde durch den Selbstzerstörungs-Zugangscode ersetzt."; +/* No comment provided by engineer. */ +"App session" = "App-Sitzung"; + /* No comment provided by engineer. */ "App version" = "App Version"; @@ -592,15 +658,36 @@ /* No comment provided by engineer. */ "Apply to" = "Anwenden auf"; +/* No comment provided by engineer. */ +"Archive" = "Archiv"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "Archiviere %lld Meldungen?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Alle Meldungen archivieren?"; + /* No comment provided by engineer. */ "Archive and upload" = "Archivieren und Hochladen"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Kontakte für spätere Chats archivieren."; +/* No comment provided by engineer. */ +"Archive report" = "Meldung archivieren"; + +/* No comment provided by engineer. */ +"Archive report?" = "Meldung archivieren?"; + +/* swipe action */ +"Archive reports" = "Meldungen archivieren"; + /* No comment provided by engineer. */ "Archived contacts" = "Archivierte Kontakte"; +/* No comment provided by engineer. */ +"archived report" = "Archivierte Meldung"; + /* No comment provided by engineer. */ "Archiving database" = "Datenbank wird archiviert"; @@ -649,6 +736,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Bilder automatisch akzeptieren"; +/* alert title */ +"Auto-accept settings" = "Einstellungen automatisch akzeptieren"; + /* No comment provided by engineer. */ "Back" = "Zurück"; @@ -670,15 +760,36 @@ /* No comment provided by engineer. */ "Bad message ID" = "Falsche Nachrichten-ID"; +/* No comment provided by engineer. */ +"Better calls" = "Verbesserte Anrufe"; + /* No comment provided by engineer. */ "Better groups" = "Bessere Gruppen"; +/* No comment provided by engineer. */ +"Better groups performance" = "Bessere Leistung von Gruppen"; + +/* No comment provided by engineer. */ +"Better message dates." = "Verbesserte Nachrichten-Datumsinformation"; + /* No comment provided by engineer. */ "Better messages" = "Verbesserungen bei Nachrichten"; /* No comment provided by engineer. */ "Better networking" = "Kontrollieren Sie Ihr Netzwerk"; +/* No comment provided by engineer. */ +"Better notifications" = "Verbesserte Benachrichtigungen"; + +/* No comment provided by engineer. */ +"Better privacy and security" = "Bessere(r) Security und Datenschutz"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Verbesserte Sicherheit ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Verbesserte Nutzer-Erfahrung"; + /* No comment provided by engineer. */ "Black" = "Schwarz"; @@ -706,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "%@ wurde blockiert"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "wurde vom Administrator blockiert"; /* No comment provided by engineer. */ @@ -716,7 +828,7 @@ "Blur for better privacy." = "Für bessere Privatsphäre verpixeln."; /* No comment provided by engineer. */ -"Blur media" = "Medium unscharf machen"; +"Blur media" = "Medium verpixeln"; /* No comment provided by engineer. */ "bold" = "fett"; @@ -739,9 +851,21 @@ /* 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)!"; +/* No comment provided by engineer. */ +"Business address" = "Geschäftliche Adresse"; + +/* No comment provided by engineer. */ +"Business chats" = "Geschäftliche Chats"; + +/* No comment provided by engineer. */ +"Businesses" = "Unternehmen"; + /* 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"; @@ -781,7 +905,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; -/* No comment provided by engineer. */ +/* alert action +alert button */ "Cancel" = "Abbrechen"; /* No comment provided by engineer. */ @@ -796,7 +921,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Die Nachricht kann nicht weitergeleitet werden"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Datei kann nicht empfangen werden"; /* snd error text */ @@ -808,6 +933,12 @@ /* No comment provided by engineer. */ "Change" = "Ändern"; +/* alert title */ +"Change automatic message deletion?" = "Automatisches Löschen von Nachrichten ändern?"; + +/* authentication reason */ +"Change chat profiles" = "Chat-Profile wechseln"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Datenbank-Passwort ändern?"; @@ -833,7 +964,7 @@ "Change self-destruct mode" = "Selbstzerstörungs-Modus ändern"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Selbstzerstörungs-Zugangscode ändern"; /* chat item text */ @@ -852,7 +983,13 @@ "changing address…" = "Wechsel der Empfängeradresse wurde gestartet…"; /* No comment provided by engineer. */ -"Chat archive" = "Datenbank Archiv"; +"Chat" = "Chat"; + +/* No comment provided by engineer. */ +"Chat already exists" = "Chat besteht bereits"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "Chat besteht bereits!"; /* No comment provided by engineer. */ "Chat colors" = "Chat-Farben"; @@ -890,13 +1027,31 @@ /* No comment provided by engineer. */ "Chat preferences" = "Chat-Präferenzen"; +/* alert message */ +"Chat preferences were changed." = "Die Chat-Präferenzen wurden geändert."; + +/* No comment provided by engineer. */ +"Chat profile" = "Benutzerprofil"; + /* No comment provided by engineer. */ "Chat theme" = "Chat-Design"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "Der Chat wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* 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!"; + /* No comment provided by engineer. */ "Chats" = "Chats"; /* 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 title */ "Check server address and try again." = "Überprüfen Sie die Serveradresse und versuchen Sie es nochmal."; /* No comment provided by engineer. */ @@ -921,16 +1076,22 @@ "Chunks uploaded" = "Daten-Pakete hochgeladen"; /* swipe action */ -"Clear" = "Löschen"; +"Clear" = "Entfernen"; /* No comment provided by engineer. */ -"Clear conversation" = "Chatinhalte löschen"; +"Clear conversation" = "Chat-Inhalte entfernen"; /* No comment provided by engineer. */ -"Clear conversation?" = "Unterhaltung löschen?"; +"Clear conversation?" = "Chat-Inhalte entfernen?"; /* No comment provided by engineer. */ -"Clear private notes?" = "Private Notizen löschen?"; +"Clear group?" = "Gruppe entfernen?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "Gruppe entfernen oder löschen?"; + +/* No comment provided by engineer. */ +"Clear private notes?" = "Private Notizen entfernen?"; /* No comment provided by engineer. */ "Clear verification" = "Überprüfung zurücknehmen"; @@ -944,6 +1105,9 @@ /* No comment provided by engineer. */ "colored" = "farbig"; +/* report reason */ +"Community guidelines violation" = "Verstoß gegen die Gemeinschaftsrichtlinien"; + /* server test step */ "Compare file" = "Datei vergleichen"; @@ -956,11 +1120,32 @@ /* No comment provided by engineer. */ "Completed" = "Abgeschlossen"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Die Nutzungsbedingungen wurden akzeptiert am: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for these operator(s): **%@**." = "Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Nutzungsbedingungen"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Die Nutzungsbedingungen werden akzeptiert am: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Die Nutzungsbedingungen der aktivierten Betreiber werden automatisch akzeptiert am: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-Server konfigurieren"; /* No comment provided by engineer. */ -"Configured %@ servers" = "Konfigurierte %@ Server"; +"Configure server operators" = "Server-Betreiber konfigurieren"; /* No comment provided by engineer. */ "Confirm" = "Bestätigen"; @@ -992,6 +1177,9 @@ /* No comment provided by engineer. */ "Confirm upload" = "Hochladen bestätigen"; +/* token status text */ +"Confirmed" = "Bestätigt"; + /* server test step */ "Connect" = "Verbinden"; @@ -1082,7 +1270,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Mit dem Desktop verbinden"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "Verbinde…"; /* No comment provided by engineer. */ @@ -1091,6 +1279,9 @@ /* No comment provided by engineer. */ "Connection and servers status." = "Verbindungs- und Server-Status."; +/* No comment provided by engineer. */ +"Connection blocked" = "Verbindung blockiert"; + /* No comment provided by engineer. */ "Connection error" = "Verbindungsfehler"; @@ -1100,12 +1291,24 @@ /* chat list item title (it should not be shown */ "connection established" = "Verbindung hergestellt"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "Die Verbindung wurde vom Server-Betreiber blockiert:\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "Verbindung noch nicht bereit."; + /* No comment provided by engineer. */ "Connection notifications" = "Verbindungsbenachrichtigungen"; /* No comment provided by engineer. */ "Connection request sent!" = "Verbindungsanfrage wurde gesendet!"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "Die Verbindung erfordert eine Neuverhandlung der Verschlüsselung."; + +/* No comment provided by engineer. */ +"Connection security" = "Verbindungs-Sicherheit"; + /* No comment provided by engineer. */ "Connection terminated" = "Verbindung beendet"; @@ -1163,11 +1366,14 @@ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "Ihre Kontakte können Nachrichten zum Löschen markieren. Sie können diese Nachrichten trotzdem anschauen."; +/* blocking reason */ +"Content violates conditions of use" = "Inhalt verletzt Nutzungsbedingungen"; + /* No comment provided by engineer. */ "Continue" = "Weiter"; /* No comment provided by engineer. */ -"Conversation deleted!" = "Unterhaltung gelöscht!"; +"Conversation deleted!" = "Chat-Inhalte entfernt!"; /* No comment provided by engineer. */ "Copy" = "Kopieren"; @@ -1178,6 +1384,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Core Version: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Abrundung Ecken"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Richtiger Name für %@?"; @@ -1185,10 +1394,10 @@ "Create" = "Erstellen"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil."; +"Create 1-time link" = "Einmal-Link erstellen"; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können."; +"Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil."; /* server test step */ "Create file" = "Datei erstellen"; @@ -1202,6 +1411,9 @@ /* No comment provided by engineer. */ "Create link" = "Link erzeugen"; +/* No comment provided by engineer. */ +"Create list" = "Liste erstellen"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Neues Profil in der [Desktop-App] erstellen (https://simplex.chat/downloads/). 💻"; @@ -1229,9 +1441,6 @@ /* copied message info */ "Created at: %@" = "Erstellt um: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Erstellt am %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Archiv-Link erzeugen"; @@ -1241,6 +1450,9 @@ /* No comment provided by engineer. */ "creator" = "Ersteller"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Der Text der aktuellen Nutzungsbedingungen konnte nicht geladen werden. Sie können die Nutzungsbedingungen unter diesem Link einsehen:"; + /* No comment provided by engineer. */ "Current Passcode" = "Aktueller Zugangscode"; @@ -1248,7 +1460,7 @@ "Current passphrase…" = "Aktuelles Passwort…"; /* No comment provided by engineer. */ -"Current profile" = "Aktueller Profil"; +"Current profile" = "Aktuelles Profil"; /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Die derzeit maximal unterstützte Dateigröße beträgt %@."; @@ -1259,6 +1471,9 @@ /* No comment provided by engineer. */ "Custom time" = "Zeit anpassen"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Anpassbares Format des Nachrichtenfelds"; + /* No comment provided by engineer. */ "Customize theme" = "Design anpassen"; @@ -1340,8 +1555,9 @@ /* No comment provided by engineer. */ "decryption errors" = "Entschlüsselungs-Fehler"; -/* pref value */ -"default (%@)" = "Voreinstellung (%@)"; +/* delete after time +pref value */ +"default (%@)" = "Default (%@)"; /* No comment provided by engineer. */ "default (no)" = "Voreinstellung (Nein)"; @@ -1349,8 +1565,8 @@ /* No comment provided by engineer. */ "default (yes)" = "Voreinstellung (Ja)"; -/* chat item action - swipe action */ +/* alert action +swipe action */ "Delete" = "Löschen"; /* No comment provided by engineer. */ @@ -1375,10 +1591,10 @@ "Delete and notify contact" = "Kontakt löschen und benachrichtigen"; /* No comment provided by engineer. */ -"Delete archive" = "Archiv löschen"; +"Delete chat" = "Chat löschen"; /* No comment provided by engineer. */ -"Delete chat archive?" = "Chat Archiv löschen?"; +"Delete chat messages from your device." = "Chat-Nachrichten von Ihrem Gerät löschen."; /* No comment provided by engineer. */ "Delete chat profile" = "Chat-Profil löschen"; @@ -1386,6 +1602,9 @@ /* No comment provided by engineer. */ "Delete chat profile?" = "Chat-Profil löschen?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Chat löschen?"; + /* No comment provided by engineer. */ "Delete connection" = "Verbindung löschen"; @@ -1431,17 +1650,20 @@ /* No comment provided by engineer. */ "Delete link?" = "Link löschen?"; +/* alert title */ +"Delete list?" = "Liste löschen?"; + /* No comment provided by engineer. */ "Delete member message?" = "Nachricht des Mitglieds löschen?"; /* No comment provided by engineer. */ "Delete message?" = "Die Nachricht löschen?"; -/* No comment provided by engineer. */ +/* alert button */ "Delete messages" = "Nachrichten löschen"; /* No comment provided by engineer. */ -"Delete messages after" = "Löschen der Nachrichten"; +"Delete messages after" = "Nachrichten löschen"; /* No comment provided by engineer. */ "Delete old database" = "Alte Datenbank löschen"; @@ -1449,6 +1671,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Alte Datenbank löschen?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Bis zu 200 Nachrichten löschen oder moderieren"; + /* No comment provided by engineer. */ "Delete pending connection?" = "Ausstehende Verbindung löschen?"; @@ -1458,6 +1683,9 @@ /* server test step */ "Delete queue" = "Lösche Warteschlange"; +/* No comment provided by engineer. */ +"Delete report" = "Meldung löschen"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Löschen Sie bis zu 20 Nachrichten auf einmal."; @@ -1488,6 +1716,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Fehler beim Löschen"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Auslieferung, selbst wenn Apple sie löscht."; + /* No comment provided by engineer. */ "Delivery" = "Zustellung"; @@ -1555,11 +1786,20 @@ "Direct messages" = "Direkte Nachrichten"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; +"Direct messages between members are prohibited in this chat." = "In diesem Chat sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; + +/* 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. */ "Disable (keep overrides)" = "Deaktivieren (vorgenommene Einstellungen bleiben erhalten)"; +/* alert title */ +"Disable automatic message deletion?" = "Automatisches Löschen von Nachrichten deaktivieren?"; + +/* alert button */ +"Disable delete messages" = "Löschen von Nachrichten deaktivieren"; + /* No comment provided by engineer. */ "Disable for all" = "Für Alle deaktivieren"; @@ -1582,7 +1822,7 @@ "Disappearing messages are prohibited in this chat." = "In diesem Chat sind verschwindende Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt."; +"Disappearing messages are prohibited." = "In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ "Disappears at" = "Verschwindet um"; @@ -1611,25 +1851,38 @@ /* 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."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Verwenden Sie keine Anmeldeinformationen mit einem Proxy."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "Sie nutzen KEIN privates Routing."; /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "SimpleX NICHT für Notrufe nutzen."; +/* No comment provided by engineer. */ +"Documents:" = "Dokumente:"; + /* No comment provided by engineer. */ "Don't create address" = "Keine Adresse erstellt"; /* No comment provided by engineer. */ "Don't enable" = "Nicht aktivieren"; +/* No comment provided by engineer. */ +"Don't miss important messages." = "Verpassen Sie keine wichtigen Nachrichten."; + /* No comment provided by engineer. */ "Don't show again" = "Nicht nochmals anzeigen"; +/* No comment provided by engineer. */ +"Done" = "Fertig"; + /* No comment provided by engineer. */ "Downgrade and open chat" = "Datenbank herabstufen und den Chat öffnen"; -/* chat item action */ +/* alert button +chat item action */ "Download" = "Herunterladen"; /* No comment provided by engineer. */ @@ -1641,6 +1894,9 @@ /* server test step */ "Download file" = "Datei herunterladen"; +/* alert action */ +"Download files" = "Dateien herunterladen"; + /* No comment provided by engineer. */ "Downloaded" = "Heruntergeladen"; @@ -1668,6 +1924,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "E2E-verschlüsselt"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "E2E-verschlüsselte Benachrichtigungen."; + /* chat item action */ "Edit" = "Bearbeiten"; @@ -1680,12 +1939,15 @@ /* No comment provided by engineer. */ "Enable (keep overrides)" = "Aktivieren (vorgenommene Einstellungen bleiben erhalten)"; -/* No comment provided by engineer. */ +/* alert title */ "Enable automatic message deletion?" = "Automatisches Löschen von Nachrichten aktivieren?"; /* No comment provided by engineer. */ "Enable camera access" = "Kamera-Zugriff aktivieren"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Für einen besseren Metadatenschutz Flux in den Netzwerk- und Servereinstellungen aktivieren."; + /* No comment provided by engineer. */ "Enable for all" = "Für Alle aktivieren"; @@ -1797,6 +2059,9 @@ /* chat item text */ "encryption re-negotiation required for %@" = "Neuaushandlung der Verschlüsselung von %@ notwendig"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "Die Neuverhandlung der Verschlüsselung läuft."; + /* No comment provided by engineer. */ "ended" = "beendet"; @@ -1845,24 +2110,36 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Fehler beim Beenden des Adresswechsels"; +/* alert title */ +"Error accepting conditions" = "Fehler beim Akzeptieren der Nutzungsbedingungen"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Fehler beim Zugriff auf die Datenbankdatei"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* alert title */ +"Error adding server" = "Fehler beim Hinzufügen des Servers"; + /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der Empfängeradresse"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Fehler beim Wechseln des Verbindungs-Profils"; + /* No comment provided by engineer. */ "Error changing role" = "Fehler beim Ändern der Rolle"; /* No comment provided by engineer. */ "Error changing setting" = "Fehler beim Ändern der Einstellung"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Fehler beim Wechseln zum Inkognito-Profil!"; + +/* No comment provided by engineer. */ +"Error checking token status" = "Fehler beim Überprüfen des Token-Status"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut."; @@ -1875,6 +2152,9 @@ /* No comment provided by engineer. */ "Error creating group link" = "Fehler beim Erzeugen des Gruppen-Links"; +/* alert title */ +"Error creating list" = "Fehler beim Erstellen der Liste"; + /* No comment provided by engineer. */ "Error creating member contact" = "Fehler beim Anlegen eines Mitglied-Kontaktes"; @@ -1884,6 +2164,9 @@ /* No comment provided by engineer. */ "Error creating profile!" = "Fehler beim Erstellen des Profils!"; +/* No comment provided by engineer. */ +"Error creating report" = "Fehler beim Erstellen der Meldung"; + /* No comment provided by engineer. */ "Error decrypting file" = "Fehler beim Entschlüsseln der Datei"; @@ -1932,14 +2215,17 @@ /* No comment provided by engineer. */ "Error joining group" = "Fehler beim Beitritt zur Gruppe"; +/* alert title */ +"Error loading servers" = "Fehler beim Laden der Server"; + /* No comment provided by engineer. */ -"Error loading %@ servers" = "Fehler beim Laden von %@ Servern"; +"Error migrating settings" = "Fehler beim Migrieren der Einstellungen"; /* No comment provided by engineer. */ "Error opening chat" = "Fehler beim Öffnen des Chats"; -/* No comment provided by engineer. */ -"Error receiving file" = "Fehler beim Empfangen der Datei"; +/* alert title */ +"Error receiving file" = "Fehler beim Herunterladen der Datei"; /* No comment provided by engineer. */ "Error reconnecting server" = "Fehler beim Wiederherstellen der Verbindung zum Server"; @@ -1947,14 +2233,20 @@ /* No comment provided by engineer. */ "Error reconnecting servers" = "Fehler beim Wiederherstellen der Verbindungen zu den Servern"; +/* alert title */ +"Error registering for notifications" = "Fehler beim Registrieren für Benachrichtigungen"; + /* No comment provided by engineer. */ "Error removing member" = "Fehler beim Entfernen des Mitglieds"; +/* alert title */ +"Error reordering lists" = "Fehler beim Umsortieren der Listen"; + /* No comment provided by engineer. */ "Error resetting statistics" = "Fehler beim Zurücksetzen der Statistiken"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Fehler beim Speichern der %@-Server"; +/* alert title */ +"Error saving chat list" = "Fehler beim Speichern der Chat-Liste"; /* No comment provided by engineer. */ "Error saving group profile" = "Fehler beim Speichern des Gruppenprofils"; @@ -1968,6 +2260,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fehler beim Speichern des Passworts in den Schlüsselbund"; +/* alert title */ +"Error saving servers" = "Fehler beim Speichern der Server"; + /* when migrating */ "Error saving settings" = "Fehler beim Abspeichern der Einstellungen"; @@ -1996,17 +2291,26 @@ "Error stopping chat" = "Fehler beim Beenden des Chats"; /* No comment provided by engineer. */ +"Error switching profile" = "Fehler beim Wechseln des Profils"; + +/* alertTitle */ "Error switching profile!" = "Fehler beim Umschalten des Profils!"; /* No comment provided by engineer. */ "Error synchronizing connection" = "Fehler beim Synchronisieren der Verbindung"; +/* No comment provided by engineer. */ +"Error testing server connection" = "Fehler beim Testen der Server-Verbindung"; + /* No comment provided by engineer. */ "Error updating group link" = "Fehler beim Aktualisieren des Gruppen-Links"; /* No comment provided by engineer. */ "Error updating message" = "Fehler beim Aktualisieren der Nachricht"; +/* alert title */ +"Error updating server" = "Fehler beim Aktualisieren des Servers"; + /* No comment provided by engineer. */ "Error updating settings" = "Fehler beim Aktualisieren der Einstellungen"; @@ -2022,8 +2326,9 @@ /* No comment provided by engineer. */ "Error: " = "Fehler: "; -/* file error text - snd error text */ +/* alert message +file error text +snd error text */ "Error: %@" = "Fehler: %@"; /* No comment provided by engineer. */ @@ -2035,11 +2340,11 @@ /* No comment provided by engineer. */ "Errors" = "Fehler"; -/* No comment provided by engineer. */ -"Even when disabled in the conversation." = "Auch wenn sie im Chat deaktiviert sind."; +/* servers error */ +"Errors in servers configuration." = "Fehler in der Server-Konfiguration."; /* No comment provided by engineer. */ -"event happened" = "event happened"; +"Even when disabled in the conversation." = "Auch wenn sie im Chat deaktiviert sind."; /* No comment provided by engineer. */ "Exit without saving" = "Beenden ohne Speichern"; @@ -2048,7 +2353,10 @@ "Expand" = "Erweitern"; /* No comment provided by engineer. */ -"expired" = "abgelaufen"; +"expired" = "Abgelaufen"; + +/* token status text */ +"Expired" = "Abgelaufen"; /* No comment provided by engineer. */ "Export database" = "Datenbank exportieren"; @@ -2074,15 +2382,30 @@ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "Schnell und ohne warten auf den Absender, bis er online ist!"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "Schnelleres löschen von Gruppen."; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung."; +/* No comment provided by engineer. */ +"Faster sending messages." = "Schnelleres versenden von Nachrichten."; + /* swipe action */ "Favorite" = "Favorit"; /* No comment provided by engineer. */ +"Favorites" = "Favoriten"; + +/* file error alert title */ "File error" = "Datei-Fehler"; +/* alert message */ +"File errors:\n%@" = "Datei-Fehler:\n%@"; + +/* file error text */ +"File is blocked by server operator:\n%@." = "Datei wurde vom Server-Betreiber blockiert:\n%@."; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen."; @@ -2099,10 +2422,10 @@ "File will be deleted from servers." = "Die Datei wird von den Servern gelöscht."; /* No comment provided by engineer. */ -"File will be received when your contact completes uploading it." = "Die Datei wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; +"File will be received when your contact completes uploading it." = "Die Datei wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; /* No comment provided by engineer. */ -"File will be received when your contact is online, please wait or check later!" = "Die Datei wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; +"File will be received when your contact is online, please wait or check later!" = "Die Datei wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; /* No comment provided by engineer. */ "File: %@" = "Datei: %@"; @@ -2117,7 +2440,7 @@ "Files and media" = "Dateien und Medien"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "In dieser Gruppe sind Dateien und Medien nicht erlaubt."; +"Files and media are prohibited." = "In dieser Gruppe sind Dateien und Medien nicht erlaubt."; /* No comment provided by engineer. */ "Files and media not allowed" = "Dateien und Medien sind nicht erlaubt"; @@ -2158,15 +2481,45 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Reparatur wird vom Gruppenmitglied nicht unterstützt"; +/* No comment provided by engineer. */ +"For all moderators" = "Für alle Moderatoren"; + +/* servers error */ +"For chat profile %@:" = "Für das Chat-Profil %@:"; + /* No comment provided by engineer. */ "For console" = "Für Konsole"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Wenn Ihr Kontakt beispielsweise Nachrichten über einen SimpleX-Chatserver empfängt, wird Ihre App diese über einen der Server von Flux versenden."; + +/* No comment provided by engineer. */ +"For me" = "Für mich"; + +/* No comment provided by engineer. */ +"For private routing" = "Für privates Routing"; + +/* No comment provided by engineer. */ +"For social media" = "Für soziale Medien"; + /* chat item action */ "Forward" = "Weiterleiten"; +/* alert title */ +"Forward %d message(s)?" = "%d Nachricht(en) weiterleiten?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Nachrichten weiterleiten und speichern"; +/* alert action */ +"Forward messages" = "Nachrichten weiterleiten"; + +/* alert message */ +"Forward messages without files?" = "Nachrichten ohne Dateien weiterleiten?"; + +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Bis zu 20 Nachrichten auf einmal weiterleiten"; + /* No comment provided by engineer. */ "forwarded" = "weitergeleitet"; @@ -2176,6 +2529,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Weitergeleitet aus"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "%lld Nachricht(en) wird/werden weitergeleitet"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut."; @@ -2203,9 +2559,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Vollständiger Name (optional)"; -/* No comment provided by engineer. */ -"Full name:" = "Vollständiger Name:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Vollständig dezentralisiert – nur für Mitglieder sichtbar."; @@ -2215,6 +2568,9 @@ /* No comment provided by engineer. */ "Further reduced battery usage" = "Weiter reduzierter Batterieverbrauch"; +/* No comment provided by engineer. */ +"Get notified when mentioned." = "Bei Erwähnung benachrichtigt werden."; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIFs und Sticker"; @@ -2260,27 +2616,6 @@ /* No comment provided by engineer. */ "Group links" = "Gruppen-Links"; -/* No comment provided by engineer. */ -"Group members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; - -/* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; - -/* No comment provided by engineer. */ -"Group members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden."; - -/* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden."; - -/* No comment provided by engineer. */ -"Group members can send files and media." = "Gruppenmitglieder können Dateien und Medien senden."; - -/* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Gruppenmitglieder können SimpleX-Links senden."; - -/* No comment provided by engineer. */ -"Group members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden."; - /* notification */ "Group message:" = "Grppennachricht:"; @@ -2306,11 +2641,17 @@ "Group will be deleted for all members - this cannot be undone!" = "Die Gruppe wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ -"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; +"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird nur bei Ihnen gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Groups" = "Gruppen"; /* No comment provided by engineer. */ "Help" = "Hilfe"; +/* No comment provided by engineer. */ +"Help admins moderating their groups." = "Helfen Sie Administratoren bei der Moderation ihrer Gruppen."; + /* No comment provided by engineer. */ "Hidden" = "Verborgen"; @@ -2342,6 +2683,12 @@ "hours" = "Stunden"; /* No comment provided by engineer. */ +"How it affects privacy" = "Wie es die Privatsphäre beeinflusst"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Wie es die Privatsphäre schützt"; + +/* alert button */ "How it works" = "Wie es funktioniert"; /* No comment provided by engineer. */ @@ -2378,16 +2725,16 @@ "Ignore" = "Ignorieren"; /* No comment provided by engineer. */ -"Image will be received when your contact completes uploading it." = "Das Bild wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; +"Image will be received when your contact completes uploading it." = "Das Bild wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; /* No comment provided by engineer. */ -"Image will be received when your contact is online, please wait or check later!" = "Das Bild wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; +"Image will be received when your contact is online, please wait or check later!" = "Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; /* No comment provided by engineer. */ "Immediately" = "Sofort"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Immun gegen Spam und Missbrauch"; +"Immune to spam" = "Immun gegen Spam und Missbrauch"; /* No comment provided by engineer. */ "Import" = "Importieren"; @@ -2407,6 +2754,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Archiv wird importiert"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch.\nWeitere Verbesserungen sind bald verfügbar!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbesserte Zustellung von Nachrichten"; @@ -2428,6 +2778,12 @@ /* No comment provided by engineer. */ "inactive" = "Inaktiv"; +/* report reason */ +"Inappropriate content" = "Unangemessener Inhalt"; + +/* report reason */ +"Inappropriate profile" = "Unangemessenes Profil"; + /* No comment provided by engineer. */ "Incognito" = "Inkognito"; @@ -2483,10 +2839,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n"; +"Instant" = "Sofort"; /* No comment provided by engineer. */ -"Instantly" = "Sofort"; +"Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n"; /* No comment provided by engineer. */ "Interface" = "Schnittstelle"; @@ -2494,6 +2850,21 @@ /* No comment provided by engineer. */ "Interface colors" = "Interface-Farben"; +/* token status text */ +"Invalid" = "Ungültig"; + +/* token status text */ +"Invalid (bad token)" = "Ungültig (falsches Token)"; + +/* token status text */ +"Invalid (expired)" = "Ungültig (abgelaufen)"; + +/* token status text */ +"Invalid (unregistered)" = "Ungültig (nicht registriert)"; + +/* token status text */ +"Invalid (wrong topic)" = "Ungültig (falsches Thema)"; + /* invalid chat data */ "invalid chat" = "Ungültiger Chat"; @@ -2524,7 +2895,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Ungültige Reaktion"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Ungültige Serveradresse!"; /* item status text */ @@ -2545,6 +2916,9 @@ /* No comment provided by engineer. */ "Invite members" = "Mitglieder einladen"; +/* No comment provided by engineer. */ +"Invite to chat" = "Zum Chat einladen"; + /* No comment provided by engineer. */ "Invite to group" = "In Gruppe einladen"; @@ -2566,6 +2940,9 @@ /* 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." = "Für die sichere Speicherung des Passworts nach dem Neustart der App und dem Wechsel des Passworts wird der iOS Schlüsselbund verwendet - dies erlaubt den Empfang von Push-Benachrichtigungen."; +/* No comment provided by engineer. */ +"IP address" = "IP-Adresse"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Unwiederbringliches löschen einer Nachricht"; @@ -2573,7 +2950,7 @@ "Irreversible message deletion is prohibited in this chat." = "In diesem Chat ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt."; +"Irreversible message deletion is prohibited." = "In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Er ermöglicht mehrere anonyme Verbindungen in einem einzigen Chat-Profil ohne Daten zwischen diesen zu teilen."; @@ -2626,16 +3003,16 @@ /* No comment provided by engineer. */ "Joining group" = "Der Gruppe beitreten"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Behalten"; /* No comment provided by engineer. */ -"Keep conversation" = "Unterhaltung behalten"; +"Keep conversation" = "Chat-Inhalte beibehalten"; /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Die App muss geöffnet bleiben, um sie vom Desktop aus nutzen zu können"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Nicht genutzte Einladung behalten?"; /* No comment provided by engineer. */ @@ -2656,6 +3033,12 @@ /* swipe action */ "Leave" = "Verlassen"; +/* No comment provided by engineer. */ +"Leave chat" = "Chat verlassen"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Chat verlassen?"; + /* No comment provided by engineer. */ "Leave group" = "Gruppe verlassen"; @@ -2683,6 +3066,15 @@ /* No comment provided by engineer. */ "Linked desktops" = "Verknüpfte Desktops"; +/* swipe action */ +"List" = "Liste"; + +/* No comment provided by engineer. */ +"List name and emoji should be different for all lists." = "Der Listenname und das Emoji sollen für alle Listen unterschiedlich sein."; + +/* No comment provided by engineer. */ +"List name..." = "Listenname..."; + /* No comment provided by engineer. */ "LIVE" = "LIVE"; @@ -2692,9 +3084,6 @@ /* No comment provided by engineer. */ "Live messages" = "Live Nachrichten"; -/* No comment provided by engineer. */ -"Local" = "Lokal"; - /* No comment provided by engineer. */ "Local name" = "Lokaler Name"; @@ -2707,24 +3096,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Sperr-Modus"; -/* No comment provided by engineer. */ -"Make a private connection" = "Stellen Sie eine private Verbindung her"; - /* No comment provided by engineer. */ "Make one message disappear" = "Eine verschwindende Nachricht verfassen"; /* No comment provided by engineer. */ "Make profile private!" = "Privates Profil erzeugen!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Stellen Sie sicher, dass die %@-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Für Alle als gelöscht markieren"; @@ -2764,15 +3144,51 @@ /* item status text */ "Member inactive" = "Mitglied inaktiv"; +/* chat feature */ +"Member reports" = "Mitglieder-Meldungen"; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "Die Rolle des Mitglieds wird auf \"%@\" geändert. Alle Chat-Mitglieder werden darüber informiert."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Die Mitgliederrolle wird auf \"%@\" geändert. Alle Mitglieder der Gruppe werden benachrichtigt."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Die Mitgliederrolle wird auf \"%@\" geändert. Das Mitglied wird eine neue Einladung erhalten."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!"; +/* 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 irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; + +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "Mitglieder können Nachrichten an Moderatoren melden."; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten versenden."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Gruppenmitglieder können Dateien und Medien versenden."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Gruppenmitglieder können SimpleX-Links versenden."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden."; + +/* No comment provided by engineer. */ +"Mention members 👋" = "Erwähnung von Mitgliedern 👋"; + /* No comment provided by engineer. */ "Menus" = "Menüs"; @@ -2807,7 +3223,7 @@ "Message reactions are prohibited in this chat." = "In diesem Chat sind Reaktionen auf Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt."; +"Message reactions are prohibited." = "In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt."; /* notification */ "message received" = "Nachricht empfangen"; @@ -2818,6 +3234,9 @@ /* No comment provided by engineer. */ "Message servers" = "Nachrichten-Server"; +/* No comment provided by engineer. */ +"Message shape" = "Nachrichten-Form"; + /* No comment provided by engineer. */ "Message source remains private." = "Die Nachrichtenquelle bleibt privat."; @@ -2842,17 +3261,23 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!"; +/* alert message */ +"Messages in this chat will never be deleted." = "Nachrichten in diesem Chat werden nie gelöscht."; + /* No comment provided by engineer. */ "Messages received" = "Empfangene Nachrichten"; /* No comment provided by engineer. */ "Messages sent" = "Gesendete Nachrichten"; -/* 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." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; +/* alert message */ +"Messages were deleted after you selected them." = "Die Nachrichten wurden gelöscht, nachdem Sie sie ausgewählt hatten."; /* 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." = "Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt."; + +/* 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." = "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"; @@ -2888,7 +3313,7 @@ "Migration is completed" = "Die Migration wurde abgeschlossen"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrationen: %@"; +"Migrations:" = "Migrationen:"; /* time unit */ "minutes" = "Minuten"; @@ -2911,27 +3336,36 @@ /* marked deleted chat item preview text */ "moderated by %@" = "Von %@ moderiert"; +/* member role */ +"moderator" = "Moderator"; + /* time unit */ "months" = "Monate"; +/* swipe action */ +"More" = "Mehr"; + /* No comment provided by engineer. */ "More improvements are coming soon!" = "Weitere Verbesserungen sind bald verfügbar!"; /* No comment provided by engineer. */ "More reliable network connection." = "Zuverlässigere Netzwerkverbindung."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Zuverlässigere Benachrichtigungen"; + /* item status description */ "Most likely this connection is deleted." = "Wahrscheinlich ist diese Verbindung gelöscht worden."; /* No comment provided by engineer. */ "Multiple chat profiles" = "Mehrere Chat-Profile"; -/* No comment provided by engineer. */ -"mute" = "Stummschalten"; - -/* swipe action */ +/* notification label action */ "Mute" = "Stummschalten"; +/* notification label action */ +"Mute all" = "Alle stummschalten"; + /* No comment provided by engineer. */ "Muted when inactive!" = "Bei Inaktivität stummgeschaltet!"; @@ -2944,21 +3378,30 @@ /* No comment provided by engineer. */ "Network connection" = "Netzwerkverbindung"; +/* No comment provided by engineer. */ +"Network decentralization" = "Dezentralisiertes Netzwerk"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen."; /* No comment provided by engineer. */ "Network management" = "Netzwerk-Verwaltung"; +/* No comment provided by engineer. */ +"Network operator" = "Netzwerk-Betreiber"; + /* No comment provided by engineer. */ "Network settings" = "Netzwerkeinstellungen"; /* No comment provided by engineer. */ "Network status" = "Netzwerkstatus"; -/* No comment provided by engineer. */ +/* delete after time */ "never" = "nie"; +/* token status text */ +"New" = "Neu"; + /* No comment provided by engineer. */ "New chat" = "Neuer Chat"; @@ -2971,15 +3414,15 @@ /* notification */ "New contact:" = "Neuer Kontakt:"; -/* No comment provided by engineer. */ -"New database archive" = "Neues Datenbankarchiv"; - /* No comment provided by engineer. */ "New desktop app!" = "Neue Desktop-App!"; /* No comment provided by engineer. */ "New display name" = "Neuer Anzeigename"; +/* notification */ +"New events" = "Neue Ereignisse"; + /* No comment provided by engineer. */ "New in %@" = "Neu in %@"; @@ -3001,6 +3444,15 @@ /* No comment provided by engineer. */ "New passphrase…" = "Neues Passwort…"; +/* No comment provided by engineer. */ +"New server" = "Neuer Server"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Jedes Mal wenn Sie die App starten, werden neue SOCKS-Anmeldeinformationen genutzt"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Für jeden Server werden neue SOCKS-Anmeldeinformationen genutzt"; + /* pref value */ "no" = "Nein"; @@ -3010,6 +3462,15 @@ /* Authentication unavailable */ "No app password" = "Kein App-Passwort"; +/* No comment provided by engineer. */ +"No chats" = "Keine Chats"; + +/* No comment provided by engineer. */ +"No chats found" = "Keine Chats gefunden"; + +/* No comment provided by engineer. */ +"No chats in list %@" = "Keine Chats in der Liste %@"; + /* No comment provided by engineer. */ "No contacts selected" = "Keine Kontakte ausgewählt"; @@ -3040,30 +3501,84 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Keine Information - es wird versucht neu zu laden"; +/* servers error */ +"No media & file servers." = "Keine Medien- und Dateiserver."; + +/* No comment provided by engineer. */ +"No message" = "Keine Nachricht"; + +/* servers error */ +"No message servers." = "Keine Nachrichten-Server."; + /* No comment provided by engineer. */ "No network connection" = "Keine Netzwerkverbindung"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Keine Genehmigung für Sprach-Aufnahmen"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Keine Genehmigung für Video-Aufnahmen"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten"; /* No comment provided by engineer. */ -"No received or sent files" = "Keine empfangenen oder gesendeten Dateien"; +"No push server" = "Lokal"; + +/* No comment provided by engineer. */ +"No received or sent files" = "Keine herunter- oder hochgeladenen Dateien"; + +/* servers error */ +"No servers for private message routing." = "Keine Server für privates Nachrichten-Routing."; + +/* servers error */ +"No servers to receive files." = "Keine Server für das Herunterladen von Dateien."; + +/* servers error */ +"No servers to receive messages." = "Keine Server für den Empfang von Nachrichten."; + +/* servers error */ +"No servers to send files." = "Keine Server für das Versenden von Dateien."; /* copied message info in history */ "no text" = "Kein Text"; +/* alert title */ +"No token!" = "Kein Token!"; + +/* No comment provided by engineer. */ +"No unread chats" = "Keine ungelesenen Chats"; + +/* No comment provided by engineer. */ +"No user identifiers." = "Keine Benutzerkennungen."; + /* No comment provided by engineer. */ "Not compatible!" = "Nicht kompatibel!"; +/* No comment provided by engineer. */ +"Notes" = "Anmerkungen"; + /* No comment provided by engineer. */ "Nothing selected" = "Nichts ausgewählt"; +/* alert title */ +"Nothing to forward!" = "Es gibt nichts zum Weiterleiten!"; + /* No comment provided by engineer. */ "Notifications" = "Benachrichtigungen"; /* No comment provided by engineer. */ "Notifications are disabled!" = "Benachrichtigungen sind deaktiviert!"; +/* alert title */ +"Notifications error" = "Benachrichtigungs-Fehler"; + +/* No comment provided by engineer. */ +"Notifications privacy" = "Datenschutz für Benachrichtigungen"; + +/* alert title */ +"Notifications status" = "Benachrichtigungs-Status"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Administratoren können nun\n- Nachrichten von Gruppenmitgliedern löschen\n- Gruppenmitglieder deaktivieren (\"Beobachter\"-Rolle)"; @@ -3071,8 +3586,8 @@ "observer" = "Beobachter"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "Aus"; /* blur media */ @@ -3084,7 +3599,7 @@ /* feature offered item */ "offered %@: %@" = "angeboten %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3093,9 +3608,6 @@ /* No comment provided by engineer. */ "Old database" = "Alte Datenbank"; -/* No comment provided by engineer. */ -"Old database archive" = "Altes Datenbankarchiv"; - /* group pref value */ "on" = "Ein"; @@ -3112,10 +3624,13 @@ "Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; +"Only chat owners can change preferences." = "Nur Chat-Eigentümer können die Präferenzen ändern."; /* No comment provided by engineer. */ -"Only delete conversation" = "Nur die Unterhaltung löschen"; +"Only client devices store user profiles, contacts, groups, and messages." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; + +/* No comment provided by engineer. */ +"Only delete conversation" = "Nur die Chat-Inhalte löschen"; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Gruppen-Präferenzen können nur von Gruppen-Eigentümern geändert werden."; @@ -3126,6 +3641,12 @@ /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "Sprachnachrichten können nur von Gruppen-Eigentümern aktiviert werden."; +/* No comment provided by engineer. */ +"Only sender and moderators see it" = "Nur Absender und Moderatoren sehen es"; + +/* No comment provided by engineer. */ +"Only you and moderators see it" = "Nur Sie und Moderatoren sehen es"; + /* No comment provided by engineer. */ "Only you can add message reactions." = "Nur Sie können Reaktionen auf Nachrichten geben."; @@ -3156,36 +3677,42 @@ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Öffnen"; +/* No comment provided by engineer. */ +"Open changes" = "Änderungen öffnen"; + /* No comment provided by engineer. */ "Open chat" = "Chat öffnen"; /* authentication reason */ "Open chat console" = "Chat-Konsole öffnen"; +/* No comment provided by engineer. */ +"Open conditions" = "Nutzungsbedingungen öffnen"; + /* No comment provided by engineer. */ "Open group" = "Gruppe öffnen"; /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; -/* No comment provided by engineer. */ -"Open server settings" = "Server-Einstellungen öffnen"; - /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; -/* authentication reason */ -"Open user profiles" = "Benutzerprofile öffnen"; - -/* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen."; - /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; +/* No comment provided by engineer. */ +"Operator" = "Betreiber"; + +/* alert title */ +"Operator server" = "Betreiber-Server"; + +/* No comment provided by engineer. */ +"Or import archive file" = "Oder importieren Sie eine Archiv-Datei"; + /* No comment provided by engineer. */ "Or paste archive link" = "Oder fügen Sie den Archiv-Link ein"; @@ -3199,17 +3726,23 @@ "Or show this code" = "Oder diesen QR-Code anzeigen"; /* No comment provided by engineer. */ -"other" = "andere"; +"Or to share privately" = "Oder zum privaten Teilen"; + +/* No comment provided by engineer. */ +"Organize chats into lists" = "Chats in Listen verwalten"; + +/* No comment provided by engineer. */ +"other" = "Andere"; /* No comment provided by engineer. */ "Other" = "Andere"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Andere %@ Server"; - /* No comment provided by engineer. */ "other errors" = "Andere Fehler"; +/* alert message */ +"Other file errors:\n%@" = "Andere(r) Datei-Fehler:\n%@"; + /* member role */ "owner" = "Eigentümer"; @@ -3231,6 +3764,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Zugangscode eingestellt!"; +/* No comment provided by engineer. */ +"Password" = "Passwort"; + /* No comment provided by engineer. */ "Password to show" = "Passwort anzeigen"; @@ -3252,14 +3788,17 @@ /* No comment provided by engineer. */ "peer-to-peer" = "Peer-to-Peer"; +/* No comment provided by engineer. */ +"pending" = "ausstehend"; + /* No comment provided by engineer. */ "Pending" = "Ausstehend"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen."; +"pending approval" = "ausstehende Genehmigung"; /* No comment provided by engineer. */ -"Periodically" = "Periodisch"; +"Periodic" = "Periodisch"; /* message decrypt error item */ "Permanent decryption error" = "Entschlüsselungsfehler"; @@ -3324,9 +3863,21 @@ /* No comment provided by engineer. */ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Bitte bewahren Sie das Passwort sicher auf, Sie können es NICHT mehr ändern, wenn Sie es vergessen haben oder verlieren."; +/* token info */ +"Please try to disable and re-enable notfications." = "Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren."; + +/* token info */ +"Please wait for token activation to complete." = "Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist."; + +/* token info */ +"Please wait for token to be registered." = "Bitte warten Sie auf die Registrierung des Tokens."; + /* No comment provided by engineer. */ "Polish interface" = "Polnische Bedienoberfläche"; +/* No comment provided by engineer. */ +"Port" = "Port"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig"; @@ -3334,10 +3885,10 @@ "Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren."; /* No comment provided by engineer. */ -"Preset server" = "Voreingestellter Server"; +"Preset server address" = "Voreingestellte Serveradresse"; /* No comment provided by engineer. */ -"Preset server address" = "Voreingestellte Serveradresse"; +"Preset servers" = "Voreingestellte Server"; /* No comment provided by engineer. */ "Preview" = "Vorschau"; @@ -3348,12 +3899,24 @@ /* No comment provided by engineer. */ "Privacy & security" = "Datenschutz & Sicherheit"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Schutz der Privatsphäre Ihrer Kunden."; + +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Datenschutz- und Nutzungsbedingungen."; + /* No comment provided by engineer. */ "Privacy redefined" = "Datenschutz neu definiert"; +/* 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."; + /* No comment provided by engineer. */ "Private filenames" = "Neutrale Dateinamen"; +/* No comment provided by engineer. */ +"Private media file names." = "Medien mit anonymisierten Dateinamen."; + /* No comment provided by engineer. */ "Private message routing" = "Privates Nachrichten-Routing"; @@ -3378,19 +3941,13 @@ /* No comment provided by engineer. */ "Profile images" = "Profil-Bilder"; -/* No comment provided by engineer. */ -"Profile name" = "Profilname"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profilname:"; - /* No comment provided by engineer. */ "Profile password" = "Passwort für Profil"; /* No comment provided by engineer. */ "Profile theme" = "Profil-Design"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Profil-Aktualisierung wird an Ihre Kontakte gesendet."; /* No comment provided by engineer. */ @@ -3405,6 +3962,9 @@ /* No comment provided by engineer. */ "Prohibit messages reactions." = "Reaktionen auf Nachrichten nicht erlauben."; +/* No comment provided by engineer. */ +"Prohibit reporting messages to moderators." = "Melden von Nachrichten an Moderatoren nicht erlauben."; + /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben."; @@ -3444,6 +4004,9 @@ /* No comment provided by engineer. */ "Proxied servers" = "Proxy-Server"; +/* No comment provided by engineer. */ +"Proxy requires password" = "Der Proxy benötigt ein Passwort"; + /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; @@ -3460,7 +4023,7 @@ "Rate the app" = "Bewerten Sie die App"; /* No comment provided by engineer. */ -"Reachable chat toolbar" = "Erreichbare Chat-Symbolleiste"; +"Reachable chat toolbar" = "Chat-Symbolleiste unten"; /* chat item menu */ "React…" = "Reagiere…"; @@ -3472,10 +4035,10 @@ "Read more" = "Mehr erfahren"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen."; @@ -3483,9 +4046,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Erfahren Sie in unserem [GitHub-Repository](https://github.com/simplex-chat/simplex-chat#readme) mehr dazu."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Erfahren Sie in unserem GitHub-Repository mehr dazu."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Bestätigungen sind deaktiviert"; @@ -3523,7 +4083,7 @@ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Die Empfängeradresse wird auf einen anderen Server geändert. Der Adresswechsel wird abgeschlossen, wenn der Absender wieder online ist."; /* No comment provided by engineer. */ -"Receiving file will be stopped." = "Der Empfang der Datei wird beendet."; +"Receiving file will be stopped." = "Das Herunterladen der Datei wird beendet."; /* No comment provided by engineer. */ "Receiving via" = "Empfangen über"; @@ -3567,8 +4127,17 @@ /* No comment provided by engineer. */ "Reduced battery usage" = "Reduzierter Batterieverbrauch"; +/* No comment provided by engineer. */ +"Register" = "Registrieren"; + +/* token info */ +"Register notification token?" = "Benachrichtigungs-Token registrieren?"; + +/* token status text */ +"Registered" = "Registriert"; + /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Ablehnen"; /* No comment provided by engineer. */ @@ -3577,6 +4146,9 @@ /* No comment provided by engineer. */ "Reject contact request" = "Kontaktanfrage ablehnen"; +/* No comment provided by engineer. */ +"rejected" = "abgelehnt"; + /* call status */ "rejected call" = "Abgelehnter Anruf"; @@ -3589,6 +4161,9 @@ /* No comment provided by engineer. */ "Remove" = "Entfernen"; +/* No comment provided by engineer. */ +"Remove archive?" = "Archiv entfernen?"; + /* No comment provided by engineer. */ "Remove image" = "Bild entfernen"; @@ -3643,6 +4218,39 @@ /* chat item action */ "Reply" = "Antwort"; +/* chat item action */ +"Report" = "Melden"; + +/* report reason */ +"Report content: only group moderators will see it." = "Inhalt melden: Nur Gruppenmoderatoren werden es sehen."; + +/* report reason */ +"Report member profile: only group moderators will see it." = "Mitgliederprofil melden: Nur Gruppenmoderatoren werden es sehen."; + +/* report reason */ +"Report other: only group moderators will see it." = "Anderes melden: Nur Gruppenmoderatoren werden es sehen."; + +/* No comment provided by engineer. */ +"Report reason?" = "Grund der Meldung?"; + +/* report reason */ +"Report spam: only group moderators will see it." = "Spam melden: Nur Gruppenmoderatoren werden es sehen."; + +/* report reason */ +"Report violation: only group moderators will see it." = "Verstoß melden: Nur Gruppenmoderatoren werden es sehen."; + +/* report in notification */ +"Report: %@" = "Meldung: %@"; + +/* No comment provided by engineer. */ +"Reporting messages to moderators is prohibited." = "Melden von Nachrichten an Moderatoren ist nicht erlaubt."; + +/* No comment provided by engineer. */ +"Reports" = "Meldungen"; + +/* chat list item title */ +"requested to connect" = "Zur Verbindung aufgefordert"; + /* No comment provided by engineer. */ "Required" = "Erforderlich"; @@ -3694,6 +4302,9 @@ /* chat item action */ "Reveal" = "Aufdecken"; +/* No comment provided by engineer. */ +"Review conditions" = "Nutzungsbedingungen einsehen"; + /* No comment provided by engineer. */ "Revoke" = "Widerrufen"; @@ -3710,18 +4321,19 @@ "Run chat" = "Chat starten"; /* No comment provided by engineer. */ -"Safely receive files" = "Dateien sicher empfangen"; +"Safely receive files" = "Dateien sicher herunterladen"; /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; -/* chat item action */ +/* alert button +chat item action */ "Save" = "Speichern"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Speichern und Kontakt benachrichtigen"; /* No comment provided by engineer. */ @@ -3733,22 +4345,19 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Gruppen-Profil sichern und aktualisieren"; -/* No comment provided by engineer. */ -"Save archive" = "Archiv speichern"; - -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Einstellungen von \"Automatisch akzeptieren\" speichern"; - /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; +/* No comment provided by engineer. */ +"Save list" = "Liste speichern"; + /* No comment provided by engineer. */ "Save passphrase and open chat" = "Passwort speichern und Chat öffnen"; /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Passwort im Schlüsselbund speichern"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Präferenzen speichern?"; /* No comment provided by engineer. */ @@ -3757,15 +4366,15 @@ /* No comment provided by engineer. */ "Save servers" = "Alle Server speichern"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Alle Server speichern?"; -/* No comment provided by engineer. */ -"Save settings?" = "Einstellungen speichern?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Begrüßungsmeldung speichern?"; +/* alert title */ +"Save your profile?" = "Ihr Profil speichern?"; + /* No comment provided by engineer. */ "saved" = "abgespeichert"; @@ -3784,6 +4393,9 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Gespeicherte WebRTC ICE-Server werden entfernt"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Es wird/werden %lld Nachricht(en) gesichert"; + /* No comment provided by engineer. */ "Scale" = "Skalieren"; @@ -3815,7 +4427,7 @@ "Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks akzeptiert."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Suchen oder fügen Sie den SimpleX-Link ein"; +"Search or paste SimpleX link" = "Suchen oder SimpleX-Link einfügen"; /* network option */ "sec" = "sek"; @@ -3847,6 +4459,9 @@ /* chat item action */ "Select" = "Auswählen"; +/* No comment provided by engineer. */ +"Select chat profile" = "Chat-Profil auswählen"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld ausgewählt"; @@ -3905,7 +4520,7 @@ "Send notifications" = "Benachrichtigungen senden"; /* No comment provided by engineer. */ -"Send notifications:" = "Benachrichtigungen senden:"; +"Send private reports" = "Private Meldungen senden"; /* No comment provided by engineer. */ "Send questions and ideas" = "Senden Sie Fragen und Ideen"; @@ -3919,7 +4534,7 @@ /* 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. */ +/* alert message */ "Sender cancelled file transfer." = "Der Absender hat die Dateiübertragung abgebrochen."; /* No comment provided by engineer. */ @@ -3979,6 +4594,12 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Über einen Proxy gesendet"; +/* No comment provided by engineer. */ +"Server" = "Server"; + +/* alert message */ +"Server added to operator %@." = "Der Server wurde dem Betreiber %@ hinzugefügt."; + /* No comment provided by engineer. */ "Server address" = "Server-Adresse"; @@ -3988,6 +4609,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel."; +/* alert title */ +"Server operator changed." = "Der Server-Betreiber wurde geändert."; + +/* No comment provided by engineer. */ +"Server operators" = "Server-Betreiber"; + +/* alert title */ +"Server protocol changed." = "Das Server-Protokoll wurde geändert."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "Server-Warteschlangen-Information: %1$@\n\nZuletzt empfangene Nachricht: %2$@"; @@ -4024,6 +4654,9 @@ /* No comment provided by engineer. */ "Set 1 day" = "Einen Tag festlegen"; +/* No comment provided by engineer. */ +"Set chat name…" = "Chat-Name festlegen…"; + /* No comment provided by engineer. */ "Set contact name…" = "Kontaktname festlegen…"; @@ -4036,6 +4669,9 @@ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen."; +/* No comment provided by engineer. */ +"Set message expiration in chats." = "Verfallsdatum von Nachrichten in Chats festlegen."; + /* profile update event chat item */ "set new contact address" = "Es wurde eine neue Kontaktadresse festgelegt"; @@ -4060,19 +4696,29 @@ /* No comment provided by engineer. */ "Settings" = "Einstellungen"; +/* alert message */ +"Settings were changed." = "Die Einstellungen wurden geändert."; + /* No comment provided by engineer. */ "Shape profile images" = "Form der Profil-Bilder"; -/* chat item action */ +/* alert action +chat item action */ "Share" = "Teilen"; /* No comment provided by engineer. */ "Share 1-time link" = "Einmal-Link teilen"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Den Einmal-Einladungslink mit einem Freund teilen"; + /* No comment provided by engineer. */ "Share address" = "Adresse teilen"; /* No comment provided by engineer. */ +"Share address publicly" = "Die Adresse öffentlich teilen"; + +/* alert title */ "Share address with contacts?" = "Die Adresse mit Kontakten teilen?"; /* No comment provided by engineer. */ @@ -4081,6 +4727,12 @@ /* No comment provided by engineer. */ "Share link" = "Link teilen"; +/* No comment provided by engineer. */ +"Share profile" = "Profil teilen"; + +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Die SimpleX-Adresse auf sozialen Medien teilen."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Teilen Sie diesen Einmal-Einladungslink"; @@ -4090,6 +4742,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Mit Kontakten teilen"; +/* No comment provided by engineer. */ +"Short link" = "Verkürzter Link"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Bei Nachrichten, die über privates Routing versendet wurden, → anzeigen."; @@ -4126,6 +4781,18 @@ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX-Adresse"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "Die SimpleX-Adresse und Einmal-Links können sicher über beliebige Messenger geteilt werden."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX-Adresse oder Einmal-Link?"; + +/* simplex link type */ +"SimpleX channel link" = "SimpleX-Kanal-Link"; + +/* 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."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft."; @@ -4142,7 +4809,7 @@ "SimpleX links" = "SimpleX-Links"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "In dieser Gruppe sind SimpleX-Links nicht erlaubt."; +"SimpleX links are prohibited." = "In dieser Gruppe sind SimpleX-Links nicht erlaubt."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX-Links sind nicht erlaubt"; @@ -4162,6 +4829,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "SimpleX-Einmal-Einladung"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Die SimpleX-Protokolle wurden von Trail of Bits überprüft."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereinfachter Inkognito-Modus"; @@ -4180,9 +4850,15 @@ /* No comment provided by engineer. */ "SMP server" = "SMP-Server"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "SOCKS-Proxy"; + /* blur media */ "Soft" = "Weich"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Einige App-Einstellungen wurden nicht migriert."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Einzelne Datei(en) wurde(n) nicht exportiert:"; @@ -4192,9 +4868,16 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Während des Imports traten ein paar nicht schwerwiegende Fehler auf:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Einige Server haben den Test nicht bestanden:\n%@"; + /* notification title */ "Somebody" = "Jemand"; +/* blocking reason +report reason */ +"Spam" = "Spam"; + /* No comment provided by engineer. */ "Square, circle, or anything in between." = "Quadratisch, kreisförmig oder irgendetwas dazwischen."; @@ -4225,9 +4908,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Chat beenden"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Chat beenden, um Datenbankaktionen zu erlauben"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Beenden Sie den Chat, um die Chat-Datenbank zu exportieren, zu importieren oder zu löschen. Solange der Chat angehalten ist, können Sie keine Nachrichten empfangen oder senden."; @@ -4235,18 +4915,18 @@ "Stop chat?" = "Chat beenden?"; /* cancel file action */ -"Stop file" = "Datei beenden"; +"Stop file" = "Herunterladen beenden"; /* No comment provided by engineer. */ -"Stop receiving file?" = "Den Empfang der Datei beenden?"; +"Stop receiving file?" = "Das Herunterladen der Datei beenden?"; /* No comment provided by engineer. */ -"Stop sending file?" = "Das Senden der Datei beenden?"; +"Stop sending file?" = "Das Hochladen der Datei beenden?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Teilen beenden"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Das Teilen der Adresse beenden?"; /* authentication reason */ @@ -4255,6 +4935,9 @@ /* No comment provided by engineer. */ "Stopping chat" = "Chat wird beendet"; +/* No comment provided by engineer. */ +"Storage" = "Ablage"; + /* No comment provided by engineer. */ "strike" = "durchstreichen"; @@ -4276,18 +4959,30 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Unterstützung von SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Während des Anrufs zwischen Audio und Video wechseln"; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Das Chat-Profil für Einmal-Einladungen wechseln"; + /* No comment provided by engineer. */ "System" = "System"; /* No comment provided by engineer. */ "System authentication" = "System-Authentifizierung"; +/* No comment provided by engineer. */ +"Tail" = "Sprechblasen-Format"; + /* No comment provided by engineer. */ "Take picture" = "Machen Sie ein Foto"; /* No comment provided by engineer. */ "Tap button " = "Schaltfläche antippen "; +/* 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 to activate profile." = "Zum Aktivieren des Profils tippen."; @@ -4312,6 +5007,9 @@ /* No comment provided by engineer. */ "TCP connection timeout" = "Timeout der TCP-Verbindung"; +/* No comment provided by engineer. */ +"TCP port for messaging" = "TCP-Port für Nachrichtenübermittlung"; + /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -4321,19 +5019,22 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; -/* No comment provided by engineer. */ +/* file error alert title */ "Temporary file error" = "Temporärer Datei-Fehler"; /* server test failure */ "Test failed at step %@." = "Der Test ist beim Schritt %@ fehlgeschlagen."; +/* No comment provided by engineer. */ +"Test notifications" = "Benachrichtigungen testen"; + /* No comment provided by engineer. */ "Test server" = "Teste Server"; /* No comment provided by engineer. */ "Test servers" = "Teste alle Server"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Tests sind fehlgeschlagen!"; /* No comment provided by engineer. */ @@ -4346,10 +5047,10 @@ "Thanks to the users – contribute via Weblate!" = "Dank der Nutzer - Tragen Sie per Weblate bei!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Die erste Plattform ohne Benutzerkennungen – Privat per Design."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; +"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 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)."; @@ -4360,6 +5061,9 @@ /* 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."; +/* 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."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Die von Ihnen akzeptierte Verbindung wird abgebrochen!"; @@ -4372,6 +5076,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen!"; +/* No comment provided by engineer. */ +"The future of messaging" = "Die nächste Generation von privatem Messaging"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Der Hash der vorherigen Nachricht unterscheidet sich."; @@ -4385,19 +5092,22 @@ "The message will be marked as moderated for all members." = "Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet."; /* No comment provided by engineer. */ -"The messages will be deleted for all members." = "Die Nachrichten werden für alle Mitglieder gelöscht werden."; +"The messages will be deleted for all members." = "Die Nachrichten werden für alle Gruppenmitglieder gelöscht."; /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden."; -/* No comment provided by engineer. */ -"The next generation of private messaging" = "Die nächste Generation von privatem Messaging"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; +"Your profile is stored on your device and only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; + +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Der zweite voreingestellte Netzwerk-Betreiber in der App!"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Wir haben das zweite Häkchen vermisst! ✅"; @@ -4406,14 +5116,23 @@ "The sender will NOT be notified" = "Der Absender wird NICHT benachrichtigt"; /* No comment provided by engineer. */ -"The servers for new connections of your current chat profile **%@**." = "Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**."; +"The servers for new connections of your current chat profile **%@**." = "Nachrichten-Server für neue Verbindungen über Ihr aktuelles Chat-Profil **%@**."; + +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Medien- und Datei-Server für neue Daten über Ihr aktuelles Chat-Profil **%@**."; /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt."; + /* No comment provided by engineer. */ "Themes" = "Design"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Diese Nutzungsbedingungen gelten auch für: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Diese Einstellungen betreffen Ihr aktuelles Profil **%@**."; @@ -4421,13 +5140,16 @@ "They can be overridden in contact and group settings." = "Sie können in den Kontakteinstellungen überschrieben werden."; /* 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." = "Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten."; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden!"; /* 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." = "Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern."; +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. Diese Aktion kann nicht rückgängig gemacht werden!"; + +/* alert message */ +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Die älteren als die ausgewählten gesendeten und empfangenen Nachrichten in diesem Chat werden gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ -"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Diese Aktion kann nicht rückgängig gemacht werden! Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren."; +"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. Diese Aktion kann nicht rückgängig gemacht werden!"; /* E2EE info chat item */ "This chat is protected by end-to-end encryption." = "Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt."; @@ -4456,9 +5178,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Das ist Ihre eigene SimpleX-Adresse!"; +/* 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."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App."; +/* No comment provided by engineer. */ +"This message was deleted or not received yet." = "Diese Nachricht wurde gelöscht oder bisher noch nicht empfangen."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**."; @@ -4478,7 +5206,7 @@ "To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; +"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."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen."; @@ -4489,15 +5217,33 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; + +/* No comment provided by engineer. */ +"To receive" = "Für den Empfang"; + +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Bitte erteilen Sie für Video-Aufnahmen die Genehmigung die Kamera zu nutzen."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Bitte erlauben Sie die Nutzung des Mikrofons, um Sprachnachrichten aufnehmen zu können."; /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Ihre Chat-Profile** ein, um Ihr verborgenes Profil zu sehen."; +/* No comment provided by engineer. */ +"To send" = "Für das Senden"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren."; + /* 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."; @@ -4507,6 +5253,9 @@ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Inkognito beim Verbinden einschalten."; +/* token status */ +"Token status: %@." = "Token-Status: %@."; + /* No comment provided by engineer. */ "Toolbar opacity" = "Deckkraft der Symbolleiste"; @@ -4555,6 +5304,9 @@ /* rcv group event chat item */ "unblocked %@" = "%@ wurde freigegeben"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Nicht ausgelieferte Nachrichten"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Unerwarteter Migrationsstatus"; @@ -4588,7 +5340,7 @@ /* No comment provided by engineer. */ "unknown servers" = "Unbekannte Relais"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Unbekannte Server!"; /* No comment provided by engineer. */ @@ -4612,10 +5364,7 @@ /* authentication reason */ "Unlock app" = "App entsperren"; -/* No comment provided by engineer. */ -"unmute" = "Stummschaltung aufheben"; - -/* swipe action */ +/* notification label action */ "Unmute" = "Stummschaltung aufheben"; /* No comment provided by engineer. */ @@ -4624,6 +5373,9 @@ /* swipe action */ "Unread" = "Ungelesen"; +/* No comment provided by engineer. */ +"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."; @@ -4639,6 +5391,9 @@ /* No comment provided by engineer. */ "Update settings?" = "Einstellungen aktualisieren?"; +/* No comment provided by engineer. */ +"Updated conditions" = "Aktualisierte Nutzungsbedingungen"; + /* rcv group event chat item */ "updated group profile" = "Aktualisiertes Gruppenprofil"; @@ -4672,12 +5427,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Verwende .onion-Hosts"; +/* No comment provided by engineer. */ +"Use %@" = "Verwende %@"; + /* No comment provided by engineer. */ "Use chat" = "Verwenden Sie Chat"; /* No comment provided by engineer. */ "Use current profile" = "Aktuelles Profil nutzen"; +/* No comment provided by engineer. */ +"Use for files" = "Für Dateien verwenden"; + +/* No comment provided by engineer. */ +"Use for messages" = "Für Nachrichten verwenden"; + /* No comment provided by engineer. */ "Use for new connections" = "Für neue Verbindungen nutzen"; @@ -4702,21 +5466,39 @@ /* No comment provided by engineer. */ "Use server" = "Server nutzen"; +/* No comment provided by engineer. */ +"Use servers" = "Verwende Server"; + +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Kurze Links verwenden (BETA)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Verwenden Sie SimpleX-Chat-Server?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "SOCKS-Proxy nutzen"; + +/* No comment provided by engineer. */ +"Use TCP port %@ when no port is specified." = "Solange kein Port konfiguriert ist, wird TCP-Port %@ genutzt."; + +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "TCP-Port 443 nur für voreingestellte Server verwenden."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Die App kann während eines Anrufs genutzt werden."; /* No comment provided by engineer. */ -"Use the app with one hand." = "Die App mit einer Hand nutzen."; +"Use the app with one hand." = "Die App mit einer Hand bedienen."; /* No comment provided by engineer. */ -"User profile" = "Benutzerprofil"; +"Use web port" = "Web-Port nutzen"; /* No comment provided by engineer. */ "User selection" = "Benutzer-Auswahl"; +/* No comment provided by engineer. */ +"Username" = "Benutzername"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Verwendung von SimpleX-Chat-Servern."; @@ -4775,17 +5557,23 @@ "video call (not e2e encrypted)" = "Videoanruf (nicht E2E verschlüsselt)"; /* No comment provided by engineer. */ -"Video will be received when your contact completes uploading it." = "Das Video wird empfangen, sobald Ihr Kontakt das Hochladen beendet hat."; +"Video will be received when your contact completes uploading it." = "Das Video wird heruntergeladen, sobald Ihr Kontakt das Hochladen beendet hat."; /* No comment provided by engineer. */ -"Video will be received when your contact is online, please wait or check later!" = "Das Video wird empfangen, wenn Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!"; +"Video will be received when your contact is online, please wait or check later!" = "Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!"; /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Videos und Dateien bis zu 1GB"; +/* No comment provided by engineer. */ +"View conditions" = "Nutzungsbedingungen anschauen"; + /* No comment provided by engineer. */ "View security code" = "Schauen Sie sich den Sicherheitscode an"; +/* No comment provided by engineer. */ +"View updated conditions" = "Aktualisierte Nutzungsbedingungen anschauen"; + /* chat feature */ "Visible history" = "Sichtbarer Nachrichtenverlauf"; @@ -4799,7 +5587,7 @@ "Voice messages are prohibited in this chat." = "In diesem Chat sind Sprachnachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "In dieser Gruppe sind Sprachnachrichten nicht erlaubt."; +"Voice messages are prohibited." = "In dieser Gruppe sind Sprachnachrichten nicht erlaubt."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Sprachnachrichten sind nicht erlaubt"; @@ -4868,7 +5656,7 @@ "when IP hidden" = "Wenn die IP-Adresse versteckt ist"; /* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen."; +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Wenn mehrere Netzwerk-Betreiber aktiviert sind, hat keiner von ihnen Metadaten, um zu erfahren, wer mit wem kommuniziert."; /* 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."; @@ -4894,7 +5682,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für Datei-Server sichtbar sein."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@."; /* No comment provided by engineer. */ @@ -4918,9 +5706,6 @@ /* No comment provided by engineer. */ "you" = "Profil"; -/* No comment provided by engineer. */ -"You" = "Profil"; - /* No comment provided by engineer. */ "You **must not** use the same database on two devices." = "Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen."; @@ -4936,6 +5721,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "Sie sind bereits mit %@ verbunden."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "Sie sind bereits mit %@ verbunden."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "Sie sind bereits mit %@ verbunden."; @@ -4981,6 +5769,9 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden."; +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Sie können die Server über die Einstellungen konfigurieren."; + /* No comment provided by engineer. */ "You can create it later" = "Sie können dies später erstellen"; @@ -5005,6 +5796,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Sie können aus den archivierten Kontakten heraus Nachrichten an %@ versenden."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Sie können einen Verbindungsnamen festlegen, um sich zu merken, mit wem der Link geteilt wurde."; + /* 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."; @@ -5014,14 +5808,11 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Sie können diese Adresse mit Ihren Kontakten teilen, um sie mit **%@** verbinden zu lassen."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten"; /* No comment provided by engineer. */ -"You can still view conversation with %@ in the list of chats." = "Sie können in der Chatliste weiterhin die Unterhaltung mit %@ einsehen."; +"You can still view conversation with %@ in the list of chats." = "Sie können in der Chat-Liste weiterhin die Unterhaltung mit %@ einsehen."; /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "Sie können die SimpleX-Sperre über die Einstellungen aktivieren."; @@ -5029,7 +5820,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Um Nachrichteninhalte zu formatieren, können Sie Markdowns verwenden:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen."; /* No comment provided by engineer. */ @@ -5048,10 +5839,10 @@ "you changed role of %@ to %@" = "Sie haben die Rolle von %1$@ auf %2$@ geändert"; /* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Sie können selbst festlegen, über welche Server Sie Ihre Nachrichten **empfangen** und an Ihre Kontakte **senden** wollen."; +"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut."; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut."; +"You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Sie haben über diese Adresse bereits eine Verbindung beantragt!"; @@ -5104,6 +5895,9 @@ /* chat list item description */ "you shared one-time link incognito" = "Sie haben Inkognito einen Einmal-Link geteilt"; +/* token info */ +"You should receive notifications." = "Sie sollten Benachrichtigungen erhalten."; + /* snd group event chat item */ "you unblocked %@" = "Sie haben %@ freigegeben"; @@ -5128,6 +5922,9 @@ /* 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 chat. Chat history will be preserved." = "Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf bleibt erhalten."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten."; @@ -5143,9 +5940,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Ihre %@-Server"; - /* No comment provided by engineer. */ "Your calls" = "Anrufe"; @@ -5155,9 +5949,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen."; +/* alert title */ +"Your chat preferences" = "Ihre Chat-Präferenzen"; + /* No comment provided by engineer. */ "Your chat profiles" = "Ihre Chat-Profile"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@)."; @@ -5167,6 +5967,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ihre Kontakte bleiben weiterhin verbunden."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Ihre Anmeldeinformationen können unverschlüsselt versendet werden."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Ihre aktuelle Chat-Datenbank wird GELÖSCHT und durch die Importierte ERSETZT."; @@ -5180,7 +5983,7 @@ "Your preferences" = "Ihre Präferenzen"; /* No comment provided by engineer. */ -"Your privacy" = "Ihre Privatsphäre"; +"Your privacy" = "Privatsphäre"; /* No comment provided by engineer. */ "Your profile" = "Mein Profil"; @@ -5189,7 +5992,10 @@ "Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.\nSimpleX-Server können Ihr Profil nicht einsehen."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen."; + +/* 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 profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert."; @@ -5198,10 +6004,10 @@ "Your random profile" = "Ihr Zufallsprofil"; /* No comment provided by engineer. */ -"Your server" = "Ihr Server"; +"Your server address" = "Ihre Serveradresse"; /* No comment provided by engineer. */ -"Your server address" = "Ihre Serveradresse"; +"Your servers" = "Ihre Server"; /* No comment provided by engineer. */ "Your settings" = "Einstellungen"; @@ -5209,9 +6015,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Ihre SimpleX-Adresse"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Ihre SMP-Server"; - -/* No comment provided by engineer. */ -"Your XFTP servers" = "Ihre XFTP-Server"; - diff --git a/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings index 0dee85ad95..e0554c9fb6 100644 --- a/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings @@ -14,5 +14,5 @@ "NSMicrophoneUsageDescription" = "SimpleX benötigt Zugriff auf das Mikrofon, um Audio- und Videoanrufe und die Aufnahme von Sprachnachrichten zu ermöglichen."; /* Privacy - Photo Library Additions Usage Description */ -"NSPhotoLibraryAddUsageDescription" = "SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder empfangene Bilder zu speichern"; +"NSPhotoLibraryAddUsageDescription" = "SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder heruntergeladene Bilder zu speichern"; diff --git a/apps/ios/en.lproj/Localizable.strings b/apps/ios/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/en.lproj/Localizable.strings +++ b/apps/ios/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 3dce4e4474..28ba0f0642 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (puede copiarse)"; @@ -31,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- mensajes de voz de hasta 5 minutos.\n- tiempo personalizado para mensajes temporales.\n- historial de edición."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 coloreado!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nuevo)"; /* No comment provided by engineer. */ "(this device v%@)" = "(este dispositivo v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuye](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -65,10 +35,7 @@ "[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. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Añadir contacto**: crea un enlace de invitación nuevo o usa un enlace recibido."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Añadir nuevo contacto**: para crear tu código QR o enlace de un uso para tu contacto."; +"**Create 1-time link**: to create and share a new invitation link." = "**Añadir contacto**: crea un enlace de invitación nuevo."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Crear grupo**: crea un grupo nuevo."; @@ -80,19 +47,22 @@ "**e2e encrypted** video call" = "Videollamada con **cifrado de extremo a extremo**"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Más privado**: comprueba los mensajes nuevos cada 20 minutos. El token del dispositivo se comparte con el servidor de SimpleX Chat, pero no cuántos contactos o mensajes tienes."; +"**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." = "**Más privado**: comprueba los mensajes nuevos cada 20 minutos. El token del dispositivo se comparte con el servidor de SimpleX Chat, pero no cuántos contactos o mensajes tienes."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación)."; +"**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." = "**Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación)."; /* 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." = "**Recuarda**: usar la misma base de datos en dos dispositivos hará que falle el descifrado de mensajes como protección de seguridad."; /* No comment provided by engineer. */ -"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes."; +"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Atención**: Si la pierdes NO podrás recuperar o cambiar la contraseña."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia."; +"**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." = "**Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia."; + +/* 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. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; @@ -154,12 +124,21 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ está verificado"; +/* No comment provided by engineer. */ +"%@ server" = "%@ servidor"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ servidores"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ subido"; /* notification title */ "%@ wants to connect!" = "¡ %@ quiere contactar!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ y %lld miembro(s) más"; @@ -170,25 +149,43 @@ "%@:" = "%@:"; /* time interval */ -"%d days" = "%d días"; +"%d days" = "%d día(s)"; + +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d archivo(s) se está(n) descargando todavía."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "La descarga ha fallado para %d archivo(s)."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d archivo(s) ha(n) sido eliminado(s)."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d archivo(s) no se ha(n) descargado."; /* time interval */ -"%d hours" = "%d horas"; +"%d hours" = "%d hora(s)"; + +/* alert title */ +"%d messages not forwarded" = "%d mensaje(s) no enviado(s)"; /* time interval */ -"%d min" = "%d minutos"; +"%d min" = "%d minuto(s)"; /* time interval */ -"%d months" = "%d meses"; +"%d months" = "%d mes(es)"; /* time interval */ -"%d sec" = "%d segundos"; +"%d sec" = "%d segundo(s)"; + +/* delete after time */ +"%d seconds(s)" = "%d segundos"; /* integrity error chat item */ -"%d skipped message(s)" = "%d mensaje(s) saltado(s"; +"%d skipped message(s)" = "%d mensaje(s) omitido(s)"; /* time interval */ -"%d weeks" = "%d semanas"; +"%d weeks" = "%d semana(s)"; /* No comment provided by engineer. */ "%lld" = "%lld"; @@ -226,9 +223,6 @@ /* No comment provided by engineer. */ "%lld new interface languages" = "%lld idiomas de interfaz nuevos"; -/* No comment provided by engineer. */ -"%lld second(s)" = "%lld segundo(s)"; - /* No comment provided by engineer. */ "%lld seconds" = "%lld segundos"; @@ -274,7 +268,8 @@ /* No comment provided by engineer. */ "0s" = "0s"; -/* time interval */ +/* delete after time +time interval */ "1 day" = "un dia"; /* time interval */ @@ -283,12 +278,23 @@ /* No comment provided by engineer. */ "1 minute" = "1 minuto"; -/* time interval */ +/* delete after time +time interval */ "1 month" = "un mes"; -/* time interval */ +/* delete after time +time interval */ "1 week" = "una semana"; +/* delete after time */ +"1 year" = "1 año"; + +/* No comment provided by engineer. */ +"1-time link" = "Enlace de un uso"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Los enlaces de un uso pueden ser usados *solamente con un contacto* - compártelos en persona o mediante cualquier aplicación de mensajería."; + /* No comment provided by engineer. */ "5 minutes" = "5 minutos"; @@ -305,13 +311,13 @@ "A new contact" = "Contacto nuevo"; /* No comment provided by engineer. */ -"A new random profile will be shared." = "Se compartirá un perfil nuevo aleatorio."; +"A new random profile will be shared." = "Compartirás un perfil nuevo aleatorio."; /* No comment provided by engineer. */ "A separate TCP connection will be used **for each chat profile you have in the app**." = "Se usará una conexión TCP independiente **por cada perfil que tengas en la aplicación**."; /* 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." = "Se usará una conexión TCP independiente **por cada contacto y miembro de grupo**.\n**Atención**: si tienes muchas conexiones, tu consumo de batería y tráfico pueden ser sustancialmente mayores y algunas conexiones pueden fallar."; +"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." = "Se usará una conexión TCP independiente **por cada contacto y miembro de grupo**.\n**Atención**: si tienes muchas conexiones, tu consumo de batería y tráfico pueden aumentar bastante y algunas conexiones pueden fallar."; /* No comment provided by engineer. */ "Abort" = "Cancelar"; @@ -323,10 +329,7 @@ "Abort changing address?" = "¿Cancelar el cambio de servidor?"; /* No comment provided by engineer. */ -"About SimpleX" = "Acerca de SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Acerca de la dirección SimpleX"; +"About operators" = "Acerca de los operadores"; /* No comment provided by engineer. */ "About SimpleX Chat" = "Sobre SimpleX Chat"; @@ -338,10 +341,13 @@ "Accent" = "Color"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Aceptar"; +/* No comment provided by engineer. */ +"Accept conditions" = "Aceptar condiciones"; + /* No comment provided by engineer. */ "Accept connection request?" = "¿Aceptar solicitud de conexión?"; @@ -349,18 +355,27 @@ "Accept contact request from %@?" = "¿Aceptar solicitud de contacto de %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Aceptar incógnito"; /* call status */ "accepted call" = "llamada aceptada"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Condiciones aceptadas"; + +/* chat list item title */ +"accepted invitation" = "invitación aceptada"; + /* No comment provided by engineer. */ "Acknowledged" = "Confirmaciones"; /* No comment provided by engineer. */ "Acknowledgement errors" = "Errores de confirmación"; +/* token status text */ +"Active" = "Activo"; + /* No comment provided by engineer. */ "Active connections" = "Conexiones activas"; @@ -368,10 +383,10 @@ "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."; /* No comment provided by engineer. */ -"Add contact" = "Añadir contacto"; +"Add friends" = "Añadir amigos"; /* No comment provided by engineer. */ -"Add preset servers" = "Añadir servidores predefinidos"; +"Add list" = "Añadir lista"; /* No comment provided by engineer. */ "Add profile" = "Añadir perfil"; @@ -382,12 +397,27 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Añadir servidores mediante el escaneo de códigos QR."; +/* No comment provided by engineer. */ +"Add team members" = "Añadir miembros del equipo"; + /* No comment provided by engineer. */ "Add to another device" = "Añadir a otro dispositivo"; +/* No comment provided by engineer. */ +"Add to list" = "Añadir a la lista"; + /* No comment provided by engineer. */ "Add welcome message" = "Añadir mensaje de bienvenida"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Añade a miembros de tu equipo a las conversaciones."; + +/* No comment provided by engineer. */ +"Added media & file servers" = "Servidores de archivos y multimedia añadidos"; + +/* No comment provided by engineer. */ +"Added message servers" = "Servidores de mensajes añadidos"; + /* No comment provided by engineer. */ "Additional accent" = "Acento adicional"; @@ -403,6 +433,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "El cambio de dirección se cancelará. Se usará la antigua dirección de recepción."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "¿Dirección o enlace de un uso?"; + +/* No comment provided by engineer. */ +"Address settings" = "Configurar dirección"; + /* member role */ "admin" = "administrador"; @@ -427,17 +463,23 @@ /* chat item text */ "agreeing encryption…" = "acordando cifrado…"; +/* No comment provided by engineer. */ +"All" = "Todo"; + /* No comment provided by engineer. */ "All app data is deleted." = "Todos los datos de la aplicación se eliminarán."; /* No comment provided by engineer. */ -"All chats and messages will be deleted - this cannot be undone!" = "Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse!"; +"All chats and messages will be deleted - this cannot be undone!" = "Se eliminarán todos los chats y mensajes. ¡No puede deshacerse!"; + +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Todos los chats se quitarán de la lista %@ y esta será eliminada."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Al introducirlo todos los datos son eliminados."; /* No comment provided by engineer. */ -"All data is private to your device." = "Todos los datos son privados y están en tu dispositivo."; +"All data is kept private on your device." = "Todos los datos son privados y están en tu dispositivo."; /* No comment provided by engineer. */ "All group members will remain connected." = "Todos los miembros del grupo permanecerán conectados."; @@ -446,17 +488,26 @@ "all members" = "todos los miembros"; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone!" = "Todos los mensajes serán borrados. ¡No podrá deshacerse!"; +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos."; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse!"; +"All messages will be deleted - this cannot be undone!" = "Todos los mensajes serán eliminados. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "¡Los mensajes nuevos de %@ estarán ocultos!"; -/* No comment provided by engineer. */ +/* profile dropdown */ "All profiles" = "Todos los perfiles"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Todos los informes serán archivados para ti."; + +/* No comment provided by engineer. */ +"All servers" = "Todos los servidores"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Todos tus contactos permanecerán conectados."; @@ -502,6 +553,9 @@ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Se permite la eliminación irreversible de mensajes. (24 horas)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Permitir informar de mensajes a los moderadores."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Se permite enviar archivos y multimedia."; @@ -556,9 +610,15 @@ /* No comment provided by engineer. */ "and %lld other events" = "y %lld evento(s) más"; +/* report reason */ +"Another reason" = "Otro motivo"; + /* 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: %@"; @@ -569,7 +629,10 @@ "App encrypts new local files (except videos)." = "Cifrado de los nuevos archivos locales (excepto vídeos)."; /* No comment provided by engineer. */ -"App icon" = "Icono aplicación"; +"App group:" = "Grupo app:"; + +/* No comment provided by engineer. */ +"App icon" = "Icono de la aplicación"; /* No comment provided by engineer. */ "App passcode" = "Código de acceso de la aplicación"; @@ -577,6 +640,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "El código de acceso será reemplazado por código de autodestrucción."; +/* No comment provided by engineer. */ +"App session" = "por sesión"; + /* No comment provided by engineer. */ "App version" = "Versión de la aplicación"; @@ -592,15 +658,36 @@ /* No comment provided by engineer. */ "Apply to" = "Aplicar a"; +/* No comment provided by engineer. */ +"Archive" = "Archivar"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "¿Archivar %lld informes?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "¿Archivar todos los informes?"; + /* No comment provided by engineer. */ "Archive and upload" = "Archivar y subir"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Archiva contactos para charlar más tarde."; +/* No comment provided by engineer. */ +"Archive report" = "Archivar informe"; + +/* No comment provided by engineer. */ +"Archive report?" = "¿Archivar informe?"; + +/* swipe action */ +"Archive reports" = "Archivar informes"; + /* No comment provided by engineer. */ "Archived contacts" = "Contactos archivados"; +/* No comment provided by engineer. */ +"archived report" = "informes archivados"; + /* No comment provided by engineer. */ "Archiving database" = "Archivando base de datos"; @@ -649,6 +736,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Aceptar imágenes automáticamente"; +/* alert title */ +"Auto-accept settings" = "Auto aceptar configuración"; + /* No comment provided by engineer. */ "Back" = "Volver"; @@ -670,15 +760,36 @@ /* No comment provided by engineer. */ "Bad message ID" = "ID de mensaje incorrecto"; +/* No comment provided by engineer. */ +"Better calls" = "Llamadas mejoradas"; + /* No comment provided by engineer. */ "Better groups" = "Grupos mejorados"; +/* No comment provided by engineer. */ +"Better groups performance" = "Rendimiento de grupos mejorado"; + +/* No comment provided by engineer. */ +"Better message dates." = "Sistema de fechas mejorado."; + /* No comment provided by engineer. */ "Better messages" = "Mensajes mejorados"; /* No comment provided by engineer. */ "Better networking" = "Uso de red mejorado"; +/* No comment provided by engineer. */ +"Better notifications" = "Notificaciones mejoradas"; + +/* No comment provided by engineer. */ +"Better privacy and security" = "Privacidad y seguridad mejoradas"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Seguridad mejorada ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Experiencia de usuario mejorada"; + /* No comment provided by engineer. */ "Black" = "Negro"; @@ -706,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "ha bloqueado a %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "bloqueado por administrador"; /* No comment provided by engineer. */ @@ -739,9 +851,21 @@ /* 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)!"; +/* No comment provided by engineer. */ +"Business address" = "Dirección empresarial"; + +/* No comment provided by engineer. */ +"Business chats" = "Chats empresariales"; + +/* No comment provided by engineer. */ +"Businesses" = "Empresas"; + /* 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"; @@ -781,7 +905,8 @@ /* No comment provided by engineer. */ "Can't message member" = "No se pueden enviar mensajes al miembro"; -/* No comment provided by engineer. */ +/* alert action +alert button */ "Cancel" = "Cancelar"; /* No comment provided by engineer. */ @@ -796,7 +921,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "No se puede reenviar el mensaje"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "No se puede recibir el archivo"; /* snd error text */ @@ -808,6 +933,12 @@ /* No comment provided by engineer. */ "Change" = "Cambiar"; +/* alert title */ +"Change automatic message deletion?" = "¿Modificar la eliminación automática de mensajes?"; + +/* authentication reason */ +"Change chat profiles" = "Cambiar perfil de usuario"; + /* No comment provided by engineer. */ "Change database passphrase?" = "¿Cambiar contraseña de la base de datos?"; @@ -833,7 +964,7 @@ "Change self-destruct mode" = "Cambiar el modo de autodestrucción"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Cambiar código autodestrucción"; /* chat item text */ @@ -852,7 +983,13 @@ "changing address…" = "cambiando de servidor…"; /* No comment provided by engineer. */ -"Chat archive" = "Archivo del chat"; +"Chat" = "Chat"; + +/* No comment provided by engineer. */ +"Chat already exists" = "El chat ya existe"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "¡El chat ya existe!"; /* No comment provided by engineer. */ "Chat colors" = "Colores del chat"; @@ -890,13 +1027,31 @@ /* No comment provided by engineer. */ "Chat preferences" = "Preferencias de Chat"; +/* alert message */ +"Chat preferences were changed." = "Las preferencias del chat han sido modificadas."; + +/* No comment provided by engineer. */ +"Chat profile" = "Perfil de usuario"; + /* No comment provided by engineer. */ "Chat theme" = "Tema de chat"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "El chat será eliminado para todos los miembros. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No puede deshacerse!"; + /* No comment provided by engineer. */ "Chats" = "Chats"; /* 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 title */ "Check server address and try again." = "Comprueba la dirección del servidor e inténtalo de nuevo."; /* No comment provided by engineer. */ @@ -930,7 +1085,13 @@ "Clear conversation?" = "¿Vaciar conversación?"; /* No comment provided by engineer. */ -"Clear private notes?" = "¿Borrar notas privadas?"; +"Clear group?" = "¿Vaciar grupo?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "¿Vaciar o eliminar grupo?"; + +/* No comment provided by engineer. */ +"Clear private notes?" = "¿Eliminar notas privadas?"; /* No comment provided by engineer. */ "Clear verification" = "Eliminar verificación"; @@ -944,6 +1105,9 @@ /* No comment provided by engineer. */ "colored" = "coloreado"; +/* report reason */ +"Community guidelines violation" = "Violación de las normas de la comunidad"; + /* server test step */ "Compare file" = "Comparar archivo"; @@ -956,11 +1120,32 @@ /* No comment provided by engineer. */ "Completed" = "Completadas"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Condiciones aceptadas el: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Las condiciones se han aceptado para el(los) operador(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for these operator(s): **%@**." = "Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Condiciones de uso"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Las condiciones serán aceptadas para el/los operador(es): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Las condiciones serán aceptadas el: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Configure servidores ICE"; /* No comment provided by engineer. */ -"Configured %@ servers" = "%@ servidores configurados"; +"Configure server operators" = "Configurar operadores de servidores"; /* No comment provided by engineer. */ "Confirm" = "Confirmar"; @@ -987,11 +1172,14 @@ "Confirm password" = "Confirmar contraseña"; /* No comment provided by engineer. */ -"Confirm that you remember database passphrase to migrate it." = "Para migrar confirma que recuerdas la frase de contraseña de la base de datos."; +"Confirm that you remember database passphrase to migrate it." = "Para migrar la base de datos confirma que recuerdas la frase de contraseña."; /* No comment provided by engineer. */ "Confirm upload" = "Confirmar subida"; +/* token status text */ +"Confirmed" = "Confirmado"; + /* server test step */ "Connect" = "Conectar"; @@ -1050,7 +1238,7 @@ "Connected to desktop" = "Conectado con ordenador"; /* No comment provided by engineer. */ -"connecting" = "conectando"; +"connecting" = "conectando..."; /* No comment provided by engineer. */ "Connecting" = "Conectando"; @@ -1082,7 +1270,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Conectando con ordenador"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "conectando…"; /* No comment provided by engineer. */ @@ -1091,6 +1279,9 @@ /* No comment provided by engineer. */ "Connection and servers status." = "Estado de tu conexión y servidores."; +/* No comment provided by engineer. */ +"Connection blocked" = "Conexión bloqueada"; + /* No comment provided by engineer. */ "Connection error" = "Error conexión"; @@ -1100,12 +1291,24 @@ /* chat list item title (it should not be shown */ "connection established" = "conexión establecida"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "Conexión bloqueada por el operador del servidor:\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "Conexión no establecida."; + /* No comment provided by engineer. */ "Connection notifications" = "Notificaciones de conexión"; /* No comment provided by engineer. */ "Connection request sent!" = "¡Solicitud de conexión enviada!"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "La conexión requiere renegociar el cifrado."; + +/* No comment provided by engineer. */ +"Connection security" = "Seguridad de conexión"; + /* No comment provided by engineer. */ "Connection terminated" = "Conexión finalizada"; @@ -1155,7 +1358,7 @@ "Contact preferences" = "Preferencias de contacto"; /* No comment provided by engineer. */ -"Contact will be deleted - this cannot be undone!" = "El contacto será eliminado. ¡No podrá deshacerse!"; +"Contact will be deleted - this cannot be undone!" = "El contacto será eliminado. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Contacts" = "Contactos"; @@ -1163,6 +1366,9 @@ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "Tus contactos sólo pueden marcar los mensajes para eliminar. Tu podrás verlos."; +/* blocking reason */ +"Content violates conditions of use" = "El contenido viola las condiciones de uso"; + /* No comment provided by engineer. */ "Continue" = "Continuar"; @@ -1178,6 +1384,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Versión Core: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Esquina"; + /* No comment provided by engineer. */ "Correct name to %@?" = "¿Corregir el nombre a %@?"; @@ -1185,10 +1394,10 @@ "Create" = "Crear"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "Crear grupo usando perfil aleatorio."; +"Create 1-time link" = "Crear enlace de un uso"; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Crea una dirección para que otras personas puedan conectar contigo."; +"Create a group using a random profile." = "Crear grupo usando perfil aleatorio."; /* server test step */ "Create file" = "Crear archivo"; @@ -1202,6 +1411,9 @@ /* No comment provided by engineer. */ "Create link" = "Crear enlace"; +/* No comment provided by engineer. */ +"Create list" = "Crear lista"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Crea perfil nuevo en la [aplicación para PC](https://simplex.Descargas/de chat/). 💻"; @@ -1229,9 +1441,6 @@ /* copied message info */ "Created at: %@" = "Creado: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Creado en %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Creando enlace al archivo"; @@ -1241,6 +1450,9 @@ /* No comment provided by engineer. */ "creator" = "creador"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace:"; + /* No comment provided by engineer. */ "Current Passcode" = "Código de Acceso"; @@ -1259,6 +1471,9 @@ /* No comment provided by engineer. */ "Custom time" = "Tiempo personalizado"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Forma personalizable de los mensajes."; + /* No comment provided by engineer. */ "Customize theme" = "Personalizar tema"; @@ -1305,7 +1520,7 @@ "Database passphrase & export" = "Base de datos y contraseña"; /* No comment provided by engineer. */ -"Database passphrase is different from saved in the keychain." = "La contraseña es distinta a la almacenada en Keychain."; +"Database passphrase is different from saved in the keychain." = "La contraseña es diferente a la almacenada en Keychain."; /* No comment provided by engineer. */ "Database passphrase is required to open chat." = "Para abrir la aplicación se requiere la contraseña de la base de datos."; @@ -1340,7 +1555,8 @@ /* No comment provided by engineer. */ "decryption errors" = "errores de descifrado"; -/* pref value */ +/* delete after time +pref value */ "default (%@)" = "predeterminado (%@)"; /* No comment provided by engineer. */ @@ -1349,8 +1565,8 @@ /* No comment provided by engineer. */ "default (yes)" = "predeterminado (sí)"; -/* chat item action - swipe action */ +/* alert action +swipe action */ "Delete" = "Eliminar"; /* No comment provided by engineer. */ @@ -1375,10 +1591,10 @@ "Delete and notify contact" = "Eliminar y notificar contacto"; /* No comment provided by engineer. */ -"Delete archive" = "Eliminar archivo"; +"Delete chat" = "Eliminar chat"; /* No comment provided by engineer. */ -"Delete chat archive?" = "¿Eliminar archivo del chat?"; +"Delete chat messages from your device." = "Elimina los mensajes del dispositivo."; /* No comment provided by engineer. */ "Delete chat profile" = "Eliminar perfil"; @@ -1386,6 +1602,9 @@ /* No comment provided by engineer. */ "Delete chat profile?" = "¿Eliminar perfil?"; +/* No comment provided by engineer. */ +"Delete chat?" = "¿Eliminar chat?"; + /* No comment provided by engineer. */ "Delete connection" = "Eliminar conexión"; @@ -1405,7 +1624,7 @@ "Delete file" = "Eliminar archivo"; /* No comment provided by engineer. */ -"Delete files and media?" = "Eliminar archivos y multimedia?"; +"Delete files and media?" = "¿Eliminar archivos y multimedia?"; /* No comment provided by engineer. */ "Delete files for all chat profiles" = "Eliminar archivos de todos los perfiles"; @@ -1431,14 +1650,17 @@ /* No comment provided by engineer. */ "Delete link?" = "¿Eliminar enlace?"; +/* alert title */ +"Delete list?" = "¿Eliminar lista?"; + /* No comment provided by engineer. */ "Delete member message?" = "¿Eliminar el mensaje de miembro?"; /* No comment provided by engineer. */ "Delete message?" = "¿Eliminar mensaje?"; -/* No comment provided by engineer. */ -"Delete messages" = "Eliminar mensaje"; +/* alert button */ +"Delete messages" = "Activar"; /* No comment provided by engineer. */ "Delete messages after" = "Eliminar en"; @@ -1449,6 +1671,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "¿Eliminar base de datos antigua?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Elimina o modera hasta 200 mensajes a la vez."; + /* No comment provided by engineer. */ "Delete pending connection?" = "¿Eliminar conexión pendiente?"; @@ -1458,6 +1683,9 @@ /* server test step */ "Delete queue" = "Eliminar cola"; +/* No comment provided by engineer. */ +"Delete report" = "Eliminar informe"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Elimina hasta 20 mensajes a la vez."; @@ -1483,11 +1711,14 @@ "deleted contact" = "contacto eliminado"; /* rcv group event chat item */ -"deleted group" = "grupo eliminado"; +"deleted group" = "ha eliminado el grupo"; /* No comment provided by engineer. */ "Deletion errors" = "Errores de eliminación"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Entregados incluso cuando Apple los descarta."; + /* No comment provided by engineer. */ "Delivery" = "Entrega"; @@ -1555,11 +1786,20 @@ "Direct messages" = "Mensajes directos"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Los mensajes directos entre miembros del grupo no están permitidos."; +"Direct messages between members are prohibited in this chat." = "Mensajes directos no permitidos entre miembros de este chat."; + +/* 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. */ "Disable (keep overrides)" = "Desactivar (conservando anulaciones)"; +/* alert title */ +"Disable automatic message deletion?" = "¿Desactivar la eliminación automática de mensajes?"; + +/* alert button */ +"Disable delete messages" = "Desactivar"; + /* No comment provided by engineer. */ "Disable for all" = "Desactivar para todos"; @@ -1582,7 +1822,7 @@ "Disappearing messages are prohibited in this chat." = "Los mensajes temporales no están permitidos en este chat."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Los mensajes temporales no están permitidos en este grupo."; +"Disappearing messages are prohibited." = "Los mensajes temporales no están permitidos en este grupo."; /* No comment provided by engineer. */ "Disappears at" = "Desaparecerá"; @@ -1594,7 +1834,7 @@ "Disconnect" = "Desconectar"; /* No comment provided by engineer. */ -"Disconnect desktop?" = "¿Desconectar ordenador?"; +"Disconnect desktop?" = "¿Desconectar del ordenador?"; /* No comment provided by engineer. */ "Discover and join groups" = "Descubre y únete a grupos"; @@ -1611,12 +1851,18 @@ /* 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."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "No uses credenciales con proxy."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "NO usar enrutamiento privado."; /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "NO uses SimpleX para llamadas de emergencia."; +/* No comment provided by engineer. */ +"Documents:" = "Documentos:"; + /* No comment provided by engineer. */ "Don't create address" = "No crear dirección SimpleX"; @@ -1624,12 +1870,19 @@ "Don't enable" = "No activar"; /* No comment provided by engineer. */ -"Don't show again" = "No mostrar de nuevo"; +"Don't miss important messages." = "No pierdas los mensajes importantes."; + +/* No comment provided by engineer. */ +"Don't show again" = "No volver a mostrar"; + +/* No comment provided by engineer. */ +"Done" = "Hecho"; /* No comment provided by engineer. */ "Downgrade and open chat" = "Degradar y abrir Chat"; -/* chat item action */ +/* alert button +chat item action */ "Download" = "Descargar"; /* No comment provided by engineer. */ @@ -1641,6 +1894,9 @@ /* server test step */ "Download file" = "Descargar archivo"; +/* alert action */ +"Download files" = "Descargar archivos"; + /* No comment provided by engineer. */ "Downloaded" = "Descargado"; @@ -1668,6 +1924,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "cifrado de extremo a extremo"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "Notificaciones cifradas E2E."; + /* chat item action */ "Edit" = "Editar"; @@ -1680,12 +1939,15 @@ /* No comment provided by engineer. */ "Enable (keep overrides)" = "Activar (conservar anulaciones)"; -/* No comment provided by engineer. */ +/* 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"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Habilitar Flux en la configuración de Red y servidores para mejorar la privacidad de los metadatos."; + /* No comment provided by engineer. */ "Enable for all" = "Activar para todos"; @@ -1738,7 +2000,7 @@ "Encrypt database?" = "¿Cifrar base de datos?"; /* No comment provided by engineer. */ -"Encrypt local files" = "Cifra archivos locales"; +"Encrypt local files" = "Cifrar archivos locales"; /* No comment provided by engineer. */ "Encrypt stored files & media" = "Cifra archivos almacenados y multimedia"; @@ -1797,6 +2059,9 @@ /* chat item text */ "encryption re-negotiation required for %@" = "se requiere renegociar el cifrado para %@"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "Renegociación de cifrado en curso."; + /* No comment provided by engineer. */ "ended" = "finalizado"; @@ -1822,7 +2087,7 @@ "Enter password above to show!" = "¡Introduce la contraseña arriba para mostrar!"; /* No comment provided by engineer. */ -"Enter server manually" = "Introduce el servidor manualmente"; +"Enter server manually" = "Añadir manualmente"; /* No comment provided by engineer. */ "Enter this device name…" = "Nombre de este dispositivo…"; @@ -1845,24 +2110,36 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Error al cancelar cambio de dirección"; +/* alert title */ +"Error accepting conditions" = "Error al aceptar las condiciones"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Error al aceptar solicitud del contacto"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Error al acceder al archivo de la base de datos"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; +/* alert title */ +"Error adding server" = "Error al añadir servidor"; + /* No comment provided by engineer. */ "Error changing address" = "Error al cambiar servidor"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Error al cambiar el perfil de conexión"; + /* No comment provided by engineer. */ "Error changing role" = "Error al cambiar rol"; /* No comment provided by engineer. */ "Error changing setting" = "Error cambiando configuración"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "¡Error al cambiar a incógnito!"; + +/* No comment provided by engineer. */ +"Error checking token status" = "Error al verificar el estado del token"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Error al conectar con el servidor de reenvío %@. Por favor, inténtalo más tarde."; @@ -1875,6 +2152,9 @@ /* No comment provided by engineer. */ "Error creating group link" = "Error al crear enlace de grupo"; +/* alert title */ +"Error creating list" = "Error al crear lista"; + /* No comment provided by engineer. */ "Error creating member contact" = "Error al establecer contacto con el miembro"; @@ -1884,6 +2164,9 @@ /* No comment provided by engineer. */ "Error creating profile!" = "¡Error al crear perfil!"; +/* No comment provided by engineer. */ +"Error creating report" = "Error al crear informe"; + /* No comment provided by engineer. */ "Error decrypting file" = "Error al descifrar el archivo"; @@ -1932,13 +2215,16 @@ /* No comment provided by engineer. */ "Error joining group" = "Error al unirte al grupo"; +/* alert title */ +"Error loading servers" = "Error al cargar servidores"; + /* No comment provided by engineer. */ -"Error loading %@ servers" = "Error al cargar servidores %@"; +"Error migrating settings" = "Error al migrar la configuración"; /* No comment provided by engineer. */ "Error opening chat" = "Error al abrir chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Error al recibir archivo"; /* No comment provided by engineer. */ @@ -1947,14 +2233,20 @@ /* No comment provided by engineer. */ "Error reconnecting servers" = "Error al reconectar con los servidores"; +/* alert title */ +"Error registering for notifications" = "Error al registrarse para notificaciones"; + /* No comment provided by engineer. */ -"Error removing member" = "Error al eliminar miembro"; +"Error removing member" = "Error al expulsar miembro"; + +/* alert title */ +"Error reordering lists" = "Error al reorganizar listas"; /* No comment provided by engineer. */ "Error resetting statistics" = "Error al restablecer las estadísticas"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Error al guardar servidores %@"; +/* alert title */ +"Error saving chat list" = "Error al guardar listas"; /* No comment provided by engineer. */ "Error saving group profile" = "Error al guardar perfil de grupo"; @@ -1968,6 +2260,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Error al guardar contraseña en Keychain"; +/* alert title */ +"Error saving servers" = "Error al guardar servidores"; + /* when migrating */ "Error saving settings" = "Error al guardar ajustes"; @@ -1996,17 +2291,26 @@ "Error stopping chat" = "Error al parar SimpleX"; /* No comment provided by engineer. */ +"Error switching profile" = "Error al cambiar perfil"; + +/* alertTitle */ "Error switching profile!" = "¡Error al cambiar perfil!"; /* No comment provided by engineer. */ "Error synchronizing connection" = "Error al sincronizar conexión"; +/* No comment provided by engineer. */ +"Error testing server connection" = "Error al testar la conexión al servidor"; + /* No comment provided by engineer. */ "Error updating group link" = "Error al actualizar enlace de grupo"; /* No comment provided by engineer. */ "Error updating message" = "Error al actualizar mensaje"; +/* alert title */ +"Error updating server" = "Error al actualizar el servidor"; + /* No comment provided by engineer. */ "Error updating settings" = "Error al actualizar configuración"; @@ -2022,8 +2326,9 @@ /* No comment provided by engineer. */ "Error: " = "Error: "; -/* file error text - snd error text */ +/* alert message +file error text +snd error text */ "Error: %@" = "Error: %@"; /* No comment provided by engineer. */ @@ -2035,11 +2340,11 @@ /* No comment provided by engineer. */ "Errors" = "Errores"; -/* No comment provided by engineer. */ -"Even when disabled in the conversation." = "Incluso si está desactivado para la conversación."; +/* servers error */ +"Errors in servers configuration." = "Error en la configuración del servidor."; /* No comment provided by engineer. */ -"event happened" = "evento ocurrido"; +"Even when disabled in the conversation." = "Incluso si está desactivado para la conversación."; /* No comment provided by engineer. */ "Exit without saving" = "Salir sin guardar"; @@ -2050,6 +2355,9 @@ /* No comment provided by engineer. */ "expired" = "expirados"; +/* token status text */ +"Expired" = "Expirado"; + /* No comment provided by engineer. */ "Export database" = "Exportar base de datos"; @@ -2074,17 +2382,32 @@ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "¡Rápido y sin necesidad de esperar a que el remitente esté en línea!"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "Eliminación más rápida de grupos."; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "Mensajería más segura y conexión más rápida."; +/* No comment provided by engineer. */ +"Faster sending messages." = "Envío más rápido de mensajes."; + /* swipe action */ "Favorite" = "Favoritos"; /* No comment provided by engineer. */ +"Favorites" = "Favoritos"; + +/* file error alert title */ "File error" = "Error de archivo"; +/* alert message */ +"File errors:\n%@" = "Error(es) de archivo\n%@"; + /* file error text */ -"File not found - most likely file was deleted or cancelled." = "Archivo no encontrado, probablemente haya sido borrado o cancelado."; +"File is blocked by server operator:\n%@." = "Archivo bloqueado por el operador del servidor\n%@."; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "Archivo no encontrado, probablemente haya sido eliminado o cancelado."; /* file error text */ "File server error: %@" = "Error del servidor de archivos: %@"; @@ -2117,7 +2440,7 @@ "Files and media" = "Archivos y multimedia"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Los archivos y multimedia no están permitidos en este grupo."; +"Files and media are prohibited." = "Los archivos y multimedia no están permitidos en este grupo."; /* No comment provided by engineer. */ "Files and media not allowed" = "Archivos y multimedia no permitidos"; @@ -2158,15 +2481,45 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Corrección no compatible con miembro del grupo"; +/* No comment provided by engineer. */ +"For all moderators" = "Para todos los moderadores"; + +/* servers error */ +"For chat profile %@:" = "Para el perfil de chat %@:"; + /* No comment provided by engineer. */ "For console" = "Para consola"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Si por ejemplo tu contacto recibe los mensajes a través de un servidor de SimpleX Chat, tu aplicación los entregará a través de un servidor de Flux."; + +/* No comment provided by engineer. */ +"For me" = "para mí"; + +/* No comment provided by engineer. */ +"For private routing" = "Para enrutamiento privado"; + +/* No comment provided by engineer. */ +"For social media" = "Para redes sociales"; + /* chat item action */ "Forward" = "Reenviar"; +/* alert title */ +"Forward %d message(s)?" = "¿Reenviar %d mensaje(s)?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Reenviar y guardar mensajes"; +/* alert action */ +"Forward messages" = "Reenviar mensajes"; + +/* alert message */ +"Forward messages without files?" = "¿Reenviar mensajes sin los archivos?"; + +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Desplazamiento de hasta 20 mensajes."; + /* No comment provided by engineer. */ "forwarded" = "reenviado"; @@ -2176,6 +2529,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Reenviado por"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "Reenviando %lld mensajes"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "El servidor de reenvío %@ no ha podido conectarse al servidor de destino %@. Por favor, intentalo más tarde."; @@ -2204,17 +2560,17 @@ "Full name (optional)" = "Nombre completo (opcional)"; /* No comment provided by engineer. */ -"Full name:" = "Nombre completo:"; +"Fully decentralized – visible only to members." = "Totalmente descentralizado. Visible sólo para los miembros."; /* No comment provided by engineer. */ -"Fully decentralized – visible only to members." = "Completamente descentralizado y sólo visible para los miembros."; - -/* No comment provided by engineer. */ -"Fully re-implemented - work in background!" = "Completamente reimplementado: ¡funciona en segundo plano!"; +"Fully re-implemented - work in background!" = "Totalmente revisado. ¡Funciona en segundo plano!"; /* No comment provided by engineer. */ "Further reduced battery usage" = "Reducción consumo de batería"; +/* No comment provided by engineer. */ +"Get notified when mentioned." = "Las menciones ahora se notifican."; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIFs y stickers"; @@ -2260,27 +2616,6 @@ /* No comment provided by engineer. */ "Group links" = "Enlaces de grupo"; -/* No comment provided by engineer. */ -"Group members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; - -/* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; - -/* No comment provided by engineer. */ -"Group members can send direct messages." = "Los miembros del grupo pueden enviar mensajes directos."; - -/* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Los miembros del grupo pueden enviar mensajes temporales."; - -/* No comment provided by engineer. */ -"Group members can send files and media." = "Los miembros del grupo pueden enviar archivos y multimedia."; - -/* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Los miembros del grupo pueden enviar enlaces SimpleX."; - -/* No comment provided by engineer. */ -"Group members can send voice messages." = "Los miembros del grupo pueden enviar mensajes de voz."; - /* notification */ "Group message:" = "Mensaje de grupo:"; @@ -2303,14 +2638,20 @@ "Group welcome message" = "Mensaje de bienvenida en grupos"; /* No comment provided by engineer. */ -"Group will be deleted for all members - this cannot be undone!" = "El grupo será eliminado para todos los miembros. ¡No podrá deshacerse!"; +"Group will be deleted for all members - this cannot be undone!" = "El grupo será eliminado para todos los miembros. ¡No puede deshacerse!"; /* No comment provided by engineer. */ -"Group will be deleted for you - this cannot be undone!" = "El grupo será eliminado para tí. ¡No podrá deshacerse!"; +"Group will be deleted for you - this cannot be undone!" = "El grupo será eliminado para tí. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Groups" = "Grupos"; /* No comment provided by engineer. */ "Help" = "Ayuda"; +/* No comment provided by engineer. */ +"Help admins moderating their groups." = "Ayuda a los admins a moderar sus grupos."; + /* No comment provided by engineer. */ "Hidden" = "Oculto"; @@ -2342,6 +2683,12 @@ "hours" = "horas"; /* No comment provided by engineer. */ +"How it affects privacy" = "Cómo afecta a la privacidad"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Cómo ayuda a la privacidad"; + +/* alert button */ "How it works" = "Cómo funciona"; /* No comment provided by engineer. */ @@ -2387,7 +2734,7 @@ "Immediately" = "Inmediatamente"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Inmune a spam y abuso"; +"Immune to spam" = "Inmune a spam y abuso"; /* No comment provided by engineer. */ "Import" = "Importar"; @@ -2407,6 +2754,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Importando archivo"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Reducción del tráfico y entrega mejorada.\n¡Pronto habrá nuevas mejoras!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Entrega de mensajes mejorada"; @@ -2428,6 +2778,12 @@ /* No comment provided by engineer. */ "inactive" = "inactivo"; +/* report reason */ +"Inappropriate content" = "Contenido inapropiado"; + +/* report reason */ +"Inappropriate profile" = "Perfil inapropiado"; + /* No comment provided by engineer. */ "Incognito" = "Incógnito"; @@ -2483,10 +2839,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "¡Las notificaciones automáticas estarán ocultas!\n"; +"Instant" = "Al instante"; /* No comment provided by engineer. */ -"Instantly" = "Al instante"; +"Instant push notifications will be hidden!\n" = "¡Las notificaciones automáticas estarán ocultas!\n"; /* No comment provided by engineer. */ "Interface" = "Interfaz"; @@ -2494,6 +2850,21 @@ /* No comment provided by engineer. */ "Interface colors" = "Colores del interfaz"; +/* token status text */ +"Invalid" = "No válido"; + +/* token status text */ +"Invalid (bad token)" = "No válido (token incorrecto)"; + +/* token status text */ +"Invalid (expired)" = "No válido (expirado)"; + +/* token status text */ +"Invalid (unregistered)" = "No válido (no registrado)"; + +/* token status text */ +"Invalid (wrong topic)" = "No válido (tópico incorrecto)"; + /* invalid chat data */ "invalid chat" = "chat no válido"; @@ -2524,7 +2895,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Respuesta no válida"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "¡Dirección de servidor no válida!"; /* item status text */ @@ -2545,6 +2916,9 @@ /* No comment provided by engineer. */ "Invite members" = "Invitar miembros"; +/* No comment provided by engineer. */ +"Invite to chat" = "Invitar al chat"; + /* No comment provided by engineer. */ "Invite to group" = "Invitar al grupo"; @@ -2566,6 +2940,9 @@ /* 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 se usará para almacenar la contraseña de forma segura después de reiniciar la aplicación o cambiar la contraseña. Esto permitirá recibir notificaciones automáticas."; +/* No comment provided by engineer. */ +"IP address" = "Dirección IP"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Eliminación irreversible del mensaje"; @@ -2573,7 +2950,7 @@ "Irreversible message deletion is prohibited in this chat." = "La eliminación irreversible de mensajes no está permitida en este chat."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "La eliminación irreversible de mensajes no está permitida en este grupo."; +"Irreversible message deletion is prohibited." = "La eliminación irreversible de mensajes no está permitida en este grupo."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Permite tener varias conexiones anónimas sin datos compartidos entre estas dentro del mismo perfil."; @@ -2626,7 +3003,7 @@ /* No comment provided by engineer. */ "Joining group" = "Entrando al grupo"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Guardar"; /* No comment provided by engineer. */ @@ -2635,7 +3012,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Mantén la aplicación abierta para usarla desde el ordenador"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "¿Guardar invitación no usada?"; /* No comment provided by engineer. */ @@ -2656,6 +3033,12 @@ /* swipe action */ "Leave" = "Salir"; +/* No comment provided by engineer. */ +"Leave chat" = "Salir del chat"; + +/* No comment provided by engineer. */ +"Leave chat?" = "¿Salir del chat?"; + /* No comment provided by engineer. */ "Leave group" = "Salir del grupo"; @@ -2683,6 +3066,15 @@ /* No comment provided by engineer. */ "Linked desktops" = "Ordenadores enlazados"; +/* swipe action */ +"List" = "Lista"; + +/* No comment provided by engineer. */ +"List name and emoji should be different for all lists." = "El nombre y el emoji deben ser diferentes en todas las listas."; + +/* No comment provided by engineer. */ +"List name..." = "Nombre de la lista..."; + /* No comment provided by engineer. */ "LIVE" = "EN VIVO"; @@ -2692,14 +3084,11 @@ /* No comment provided by engineer. */ "Live messages" = "Mensajes en vivo"; -/* No comment provided by engineer. */ -"Local" = "Local"; - /* No comment provided by engineer. */ "Local name" = "Nombre local"; /* No comment provided by engineer. */ -"Local profile data only" = "Sólo datos del perfil local"; +"Local profile data only" = "Eliminar sólo el perfil"; /* No comment provided by engineer. */ "Lock after" = "Bloquear en"; @@ -2707,24 +3096,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Modo bloqueo"; -/* No comment provided by engineer. */ -"Make a private connection" = "Establecer una conexión privada"; - /* No comment provided by engineer. */ "Make one message disappear" = "Escribir un mensaje temporal"; /* No comment provided by engineer. */ "Make profile private!" = "¡Hacer perfil privado!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Asegúrate de que las direcciones del servidor %@ tienen el formato correcto, están separadas por líneas y no duplicadas (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Asegúrate de que las direcciones del servidor WebRTC ICE tienen el formato correcto, están separadas por líneas y no duplicadas."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Muchos se preguntarán: *si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Marcar como eliminado para todos"; @@ -2764,6 +3144,12 @@ /* item status text */ "Member inactive" = "Miembro inactivo"; +/* chat feature */ +"Member reports" = "Informes de miembros"; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "El rol del miembro cambiará a \"%@\" y todos serán notificados."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "El rol del miembro cambiará a \"%@\" y se notificará al grupo."; @@ -2771,7 +3157,37 @@ "Member role will be changed to \"%@\". The member will receive a new invitation." = "El rol del miembro cambiará a \"%@\" y recibirá una invitación nueva."; /* No comment provided by engineer. */ -"Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No podrá deshacerse!"; +"Member will be removed from chat - this cannot be undone!" = "El miembro será eliminado del chat. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No puede deshacerse!"; + +/* 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 irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; + +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "Los miembros pueden informar de mensajes a los moderadores."; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Los miembros del grupo pueden enviar mensajes directos."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Los miembros del grupo pueden enviar mensajes temporales."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Los miembros del grupo pueden enviar archivos y multimedia."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Los miembros del grupo pueden enviar enlaces SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Los miembros del grupo pueden enviar mensajes de voz."; + +/* No comment provided by engineer. */ +"Mention members 👋" = "Menciona a miembros 👋"; /* No comment provided by engineer. */ "Menus" = "Menus"; @@ -2807,7 +3223,7 @@ "Message reactions are prohibited in this chat." = "Las reacciones a los mensajes no están permitidas en este chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Las reacciones a los mensajes no están permitidas en este grupo."; +"Message reactions are prohibited." = "Las reacciones a los mensajes no están permitidas en este grupo."; /* notification */ "message received" = "mensaje recibido"; @@ -2818,6 +3234,9 @@ /* No comment provided by engineer. */ "Message servers" = "Servidores de mensajes"; +/* No comment provided by engineer. */ +"Message shape" = "Forma del mensaje"; + /* No comment provided by engineer. */ "Message source remains private." = "El autor del mensaje se mantiene privado."; @@ -2842,12 +3261,18 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "¡Los mensajes de %@ serán mostrados!"; +/* alert message */ +"Messages in this chat will never be deleted." = "Los mensajes de esta conversación nunca se eliminan."; + /* No comment provided by engineer. */ "Messages received" = "Mensajes recibidos"; /* No comment provided by engineer. */ "Messages sent" = "Mensajes enviados"; +/* alert message */ +"Messages were deleted after you selected them." = "Los mensajes han sido eliminados después de seleccionarlos."; + /* 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." = "Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo** con secreto perfecto hacía adelante, repudio y recuperación tras ataque."; @@ -2888,7 +3313,7 @@ "Migration is completed" = "Migración completada"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migraciones: %@"; +"Migrations:" = "Migraciones:"; /* time unit */ "minutes" = "minutos"; @@ -2911,27 +3336,36 @@ /* marked deleted chat item preview text */ "moderated by %@" = "moderado por %@"; +/* member role */ +"moderator" = "moderador"; + /* time unit */ "months" = "meses"; +/* swipe action */ +"More" = "Más"; + /* No comment provided by engineer. */ "More improvements are coming soon!" = "¡Pronto habrá más mejoras!"; /* No comment provided by engineer. */ "More reliable network connection." = "Conexión de red más fiable."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Notificaciones más fiables"; + /* item status description */ "Most likely this connection is deleted." = "Probablemente la conexión ha sido eliminada."; /* No comment provided by engineer. */ "Multiple chat profiles" = "Múltiples perfiles"; -/* No comment provided by engineer. */ -"mute" = "silenciar"; - -/* swipe action */ +/* notification label action */ "Mute" = "Silenciar"; +/* notification label action */ +"Mute all" = "Silenciar todo"; + /* No comment provided by engineer. */ "Muted when inactive!" = "¡Silenciado cuando está inactivo!"; @@ -2944,21 +3378,30 @@ /* No comment provided by engineer. */ "Network connection" = "Conexión de red"; +/* No comment provided by engineer. */ +"Network decentralization" = "Descentralización de la 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."; /* No comment provided by engineer. */ "Network management" = "Gestión de la red"; +/* No comment provided by engineer. */ +"Network operator" = "Operador de red"; + /* No comment provided by engineer. */ "Network settings" = "Configuración de red"; /* No comment provided by engineer. */ "Network status" = "Estado de la red"; -/* No comment provided by engineer. */ +/* delete after time */ "never" = "nunca"; +/* token status text */ +"New" = "Nuevo"; + /* No comment provided by engineer. */ "New chat" = "Nuevo chat"; @@ -2971,15 +3414,15 @@ /* notification */ "New contact:" = "Contacto nuevo:"; -/* No comment provided by engineer. */ -"New database archive" = "Nuevo archivo de bases de datos"; - /* No comment provided by engineer. */ "New desktop app!" = "Nueva aplicación para PC!"; /* No comment provided by engineer. */ "New display name" = "Nuevo nombre mostrado"; +/* notification */ +"New events" = "Eventos nuevos"; + /* No comment provided by engineer. */ "New in %@" = "Nuevo en %@"; @@ -3001,6 +3444,15 @@ /* No comment provided by engineer. */ "New passphrase…" = "Contraseña nueva…"; +/* No comment provided by engineer. */ +"New server" = "Servidor nuevo"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Se usarán credenciales SOCKS nuevas cada vez que inicies la aplicación."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Se usarán credenciales SOCKS nuevas para cada servidor."; + /* pref value */ "no" = "no"; @@ -3010,6 +3462,15 @@ /* Authentication unavailable */ "No app password" = "Sin contraseña de la aplicación"; +/* No comment provided by engineer. */ +"No chats" = "Sin chats"; + +/* No comment provided by engineer. */ +"No chats found" = "Ningún chat encontrado"; + +/* No comment provided by engineer. */ +"No chats in list %@" = "Sin chats en la lista %@"; + /* No comment provided by engineer. */ "No contacts selected" = "Ningún contacto seleccionado"; @@ -3040,30 +3501,84 @@ /* No comment provided by engineer. */ "No info, try to reload" = "No hay información, intenta recargar"; +/* servers error */ +"No media & file servers." = "Sin servidores para archivos y multimedia."; + +/* No comment provided by engineer. */ +"No message" = "Ningún mensaje"; + +/* servers error */ +"No message servers." = "Sin servidores para mensajes."; + /* No comment provided by engineer. */ "No network connection" = "Sin conexión de red"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Sin permiso para grabación de voz"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Sin permiso para grabación de vídeo"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Sin permiso para grabar mensajes de voz"; +/* No comment provided by engineer. */ +"No push server" = "Sin servidores push"; + /* No comment provided by engineer. */ "No received or sent files" = "Sin archivos recibidos o enviados"; +/* servers error */ +"No servers for private message routing." = "Sin servidores para enrutamiento privado."; + +/* servers error */ +"No servers to receive files." = "Sin servidores para recibir archivos."; + +/* servers error */ +"No servers to receive messages." = "Sin servidores para recibir mensajes."; + +/* servers error */ +"No servers to send files." = "Sin servidores para enviar archivos."; + /* copied message info in history */ "no text" = "sin texto"; +/* alert title */ +"No token!" = "¡Sin token!"; + +/* No comment provided by engineer. */ +"No unread chats" = "Ningún chat sin leer"; + +/* No comment provided by engineer. */ +"No user identifiers." = "Sin identificadores de usuario."; + /* No comment provided by engineer. */ "Not compatible!" = "¡No compatible!"; +/* No comment provided by engineer. */ +"Notes" = "Notas"; + /* No comment provided by engineer. */ "Nothing selected" = "Nada seleccionado"; +/* alert title */ +"Nothing to forward!" = "¡Nada para reenviar!"; + /* No comment provided by engineer. */ "Notifications" = "Notificaciones"; /* No comment provided by engineer. */ "Notifications are disabled!" = "¡Las notificaciones están desactivadas!"; +/* alert title */ +"Notifications error" = "Error en notificaciones"; + +/* No comment provided by engineer. */ +"Notifications privacy" = "Privacidad en las notificaciones"; + +/* alert title */ +"Notifications status" = "Estado notificaciones"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Ahora los administradores pueden:\n- eliminar mensajes de los miembros.\n- desactivar el rol miembro (a rol \"observador\")"; @@ -3071,8 +3586,8 @@ "observer" = "observador"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "desactivado"; /* blur media */ @@ -3084,7 +3599,7 @@ /* feature offered item */ "offered %@: %@" = "ofrecido %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3093,9 +3608,6 @@ /* No comment provided by engineer. */ "Old database" = "Base de datos antigua"; -/* No comment provided by engineer. */ -"Old database archive" = "Archivo de bases de datos antiguas"; - /* group pref value */ "on" = "Activado"; @@ -3112,10 +3624,13 @@ "Onion hosts will not be used." = "No se usarán hosts .onion."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**."; +"Only chat owners can change preferences." = "Sólo los propietarios del chat pueden cambiar las preferencias."; /* No comment provided by engineer. */ -"Only delete conversation" = "Sólo borrar la conversación"; +"Only client devices store user profiles, contacts, groups, and messages." = "Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**."; + +/* No comment provided by engineer. */ +"Only delete conversation" = "Eliminar sólo la conversación"; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Sólo los propietarios pueden modificar las preferencias del grupo."; @@ -3126,6 +3641,12 @@ /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "Sólo los propietarios del grupo pueden activar los mensajes de voz."; +/* No comment provided by engineer. */ +"Only sender and moderators see it" = "Solo el remitente y el moderador pueden verlo"; + +/* No comment provided by engineer. */ +"Only you and moderators see it" = "Solo tú y los moderadores podéis verlo"; + /* No comment provided by engineer. */ "Only you can add message reactions." = "Sólo tú puedes añadir reacciones a los mensajes."; @@ -3156,36 +3677,42 @@ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sólo tu contacto puede enviar mensajes de voz."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Abrir"; +/* No comment provided by engineer. */ +"Open changes" = "Abrir cambios"; + /* No comment provided by engineer. */ "Open chat" = "Abrir chat"; /* authentication reason */ "Open chat console" = "Abrir consola de Chat"; +/* No comment provided by engineer. */ +"Open conditions" = "Abrir condiciones"; + /* No comment provided by engineer. */ "Open group" = "Grupo abierto"; /* authentication reason */ "Open migration to another device" = "Abrir menú migración a otro dispositivo"; -/* No comment provided by engineer. */ -"Open server settings" = "Abrir configuración del servidor"; - /* No comment provided by engineer. */ "Open Settings" = "Abrir Configuración"; -/* authentication reason */ -"Open user profiles" = "Abrir perfil de usuario"; - -/* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protocolo y código abiertos: cualquiera puede usar los servidores."; - /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; +/* No comment provided by engineer. */ +"Operator" = "Operador"; + +/* alert title */ +"Operator server" = "Servidor del operador"; + +/* No comment provided by engineer. */ +"Or import archive file" = "O importa desde un archivo"; + /* No comment provided by engineer. */ "Or paste archive link" = "O pegar enlace del archivo"; @@ -3196,7 +3723,13 @@ "Or securely share this file link" = "O comparte de forma segura este enlace al archivo"; /* No comment provided by engineer. */ -"Or show this code" = "O muestra este código QR"; +"Or show this code" = "O muestra el código QR"; + +/* No comment provided by engineer. */ +"Or to share privately" = "O para compartir en privado"; + +/* No comment provided by engineer. */ +"Organize chats into lists" = "Organiza tus chats en listas"; /* No comment provided by engineer. */ "other" = "otros"; @@ -3204,12 +3737,12 @@ /* No comment provided by engineer. */ "Other" = "Otro"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Otros servidores %@"; - /* No comment provided by engineer. */ "other errors" = "otros errores"; +/* alert message */ +"Other file errors:\n%@" = "Otro(s) error(es) de archivo.\n%@"; + /* member role */ "owner" = "propietario"; @@ -3231,6 +3764,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "¡Código de acceso guardado!"; +/* No comment provided by engineer. */ +"Password" = "Contraseña"; + /* No comment provided by engineer. */ "Password to show" = "Contraseña para hacerlo visible"; @@ -3252,14 +3788,17 @@ /* No comment provided by engineer. */ "peer-to-peer" = "p2p"; +/* No comment provided by engineer. */ +"pending" = "pendiente"; + /* No comment provided by engineer. */ "Pending" = "Pendientes"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Las personas pueden conectarse contigo solo mediante los enlaces que compartes."; +"pending approval" = "pendiente de aprobación"; /* No comment provided by engineer. */ -"Periodically" = "Periódicamente"; +"Periodic" = "Periódicamente"; /* message decrypt error item */ "Permanent decryption error" = "Error permanente descifrado"; @@ -3324,20 +3863,32 @@ /* No comment provided by engineer. */ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes."; +/* token info */ +"Please try to disable and re-enable notfications." = "Por favor, intenta desactivar y reactivar las notificaciones."; + +/* token info */ +"Please wait for token activation to complete." = "Por favor, espera a que el token de activación se complete."; + +/* token info */ +"Please wait for token to be registered." = "Por favor, espera a que el token se registre."; + /* No comment provided by engineer. */ "Polish interface" = "Interfaz en polaco"; +/* No comment provided by engineer. */ +"Port" = "Puerto"; + /* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta"; +"Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella del certificado en la dirección del servidor es incorrecta"; /* 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 server" = "Servidor predefinido"; +"Preset server address" = "Dirección predefinida del servidor"; /* No comment provided by engineer. */ -"Preset server address" = "Dirección del servidor predefinida"; +"Preset servers" = "Servidores predefinidos"; /* No comment provided by engineer. */ "Preview" = "Vista previa"; @@ -3348,12 +3899,24 @@ /* No comment provided by engineer. */ "Privacy & security" = "Seguridad y Privacidad"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Privacidad para tus clientes."; + +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Política de privacidad y condiciones de uso."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacidad redefinida"; +/* 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."; + /* No comment provided by engineer. */ "Private filenames" = "Nombres de archivos privados"; +/* No comment provided by engineer. */ +"Private media file names." = "Nombres privados en archivos de media."; + /* No comment provided by engineer. */ "Private message routing" = "Enrutamiento privado de mensajes"; @@ -3370,7 +3933,7 @@ "Private routing error" = "Error de enrutamiento privado"; /* No comment provided by engineer. */ -"Profile and server connections" = "Datos del perfil y conexiones"; +"Profile and server connections" = "Eliminar perfil y conexiones"; /* No comment provided by engineer. */ "Profile image" = "Imagen del perfil"; @@ -3378,19 +3941,13 @@ /* No comment provided by engineer. */ "Profile images" = "Forma de los perfiles"; -/* No comment provided by engineer. */ -"Profile name" = "Nombre del perfil"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nombre del perfil:"; - /* No comment provided by engineer. */ "Profile password" = "Contraseña del perfil"; /* No comment provided by engineer. */ "Profile theme" = "Tema del perfil"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "La actualización del perfil se enviará a tus contactos."; /* No comment provided by engineer. */ @@ -3405,6 +3962,9 @@ /* No comment provided by engineer. */ "Prohibit messages reactions." = "No se permiten reacciones a los mensajes."; +/* No comment provided by engineer. */ +"Prohibit reporting messages to moderators." = "No se permite informar de mensajes a los moderadores."; + /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "No se permiten mensajes directos entre miembros."; @@ -3421,7 +3981,7 @@ "Prohibit sending voice messages." = "No se permiten mensajes de voz."; /* No comment provided by engineer. */ -"Protect app screen" = "Proteger la pantalla de la aplicación"; +"Protect app screen" = "Proteger la pantalla"; /* No comment provided by engineer. */ "Protect IP address" = "Proteger dirección IP"; @@ -3445,7 +4005,10 @@ "Proxied servers" = "Servidores con proxy"; /* No comment provided by engineer. */ -"Push notifications" = "Notificaciones automáticas"; +"Proxy requires password" = "El proxy requiere contraseña"; + +/* No comment provided by engineer. */ +"Push notifications" = "Notificaciones push"; /* No comment provided by engineer. */ "Push server" = "Servidor push"; @@ -3460,7 +4023,7 @@ "Rate the app" = "Valora la aplicación"; /* No comment provided by engineer. */ -"Reachable chat toolbar" = "Barra de herramientas accesible"; +"Reachable chat toolbar" = "Barra de menú accesible"; /* chat item menu */ "React…" = "Reacciona…"; @@ -3469,23 +4032,20 @@ "Read" = "Leer"; /* No comment provided by engineer. */ -"Read more" = "Conoce más"; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"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)."; +/* 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)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Conoce más en nuestro repositorio GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Las confirmaciones están desactivadas"; @@ -3550,7 +4110,7 @@ "Reconnect all servers?" = "¿Reconectar todos los servidores?"; /* No comment provided by engineer. */ -"Reconnect server to force message delivery. It uses additional traffic." = "Reconectar el servidor para forzar la entrega de mensajes. Usa tráfico adicional."; +"Reconnect server to force message delivery. It uses additional traffic." = "Reconectar con el servidor para forzar la entrega de mensajes. Se usa tráfico adicional."; /* No comment provided by engineer. */ "Reconnect server?" = "¿Reconectar servidor?"; @@ -3567,8 +4127,17 @@ /* No comment provided by engineer. */ "Reduced battery usage" = "Reducción del uso de batería"; +/* No comment provided by engineer. */ +"Register" = "Registrar"; + +/* token info */ +"Register notification token?" = "¿Registrar el token de notificaciones?"; + +/* token status text */ +"Registered" = "Registrado"; + /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Rechazar"; /* No comment provided by engineer. */ @@ -3577,6 +4146,9 @@ /* No comment provided by engineer. */ "Reject contact request" = "Rechazar solicitud de contacto"; +/* No comment provided by engineer. */ +"rejected" = "rechazado"; + /* call status */ "rejected call" = "llamada rechazada"; @@ -3589,6 +4161,9 @@ /* No comment provided by engineer. */ "Remove" = "Eliminar"; +/* No comment provided by engineer. */ +"Remove archive?" = "¿Eliminar archivo?"; + /* No comment provided by engineer. */ "Remove image" = "Eliminar imagen"; @@ -3611,7 +4186,7 @@ "removed contact address" = "dirección de contacto eliminada"; /* profile update event chat item */ -"removed profile picture" = "imagen de perfil eliminada"; +"removed profile picture" = "ha eliminado la imagen del perfil"; /* rcv group event chat item */ "removed you" = "te ha expulsado"; @@ -3643,6 +4218,39 @@ /* chat item action */ "Reply" = "Responder"; +/* chat item action */ +"Report" = "Informe"; + +/* report reason */ +"Report content: only group moderators will see it." = "Informar de contenido: sólo los moderadores del grupo lo verán."; + +/* report reason */ +"Report member profile: only group moderators will see it." = "Informar del perfil de un miembro: sólo los moderadores del grupo lo verán."; + +/* report reason */ +"Report other: only group moderators will see it." = "Informar de otros: sólo los moderadores del grupo lo verán."; + +/* No comment provided by engineer. */ +"Report reason?" = "¿Motivo del informe?"; + +/* report reason */ +"Report spam: only group moderators will see it." = "Informar de spam: sólo los moderadores del grupo lo verán."; + +/* report reason */ +"Report violation: only group moderators will see it." = "Informar de violación: sólo los moderadores del grupo lo verán."; + +/* report in notification */ +"Report: %@" = "Informe: %@"; + +/* No comment provided by engineer. */ +"Reporting messages to moderators is prohibited." = "No se permite informar de mensajes a los moderadores."; + +/* No comment provided by engineer. */ +"Reports" = "Informes"; + +/* chat list item title */ +"requested to connect" = "solicitado para conectar"; + /* No comment provided by engineer. */ "Required" = "Obligatorio"; @@ -3694,6 +4302,9 @@ /* chat item action */ "Reveal" = "Revelar"; +/* No comment provided by engineer. */ +"Review conditions" = "Revisar condiciones"; + /* No comment provided by engineer. */ "Revoke" = "Revocar"; @@ -3715,13 +4326,14 @@ /* No comment provided by engineer. */ "Safer groups" = "Grupos más seguros"; -/* chat item action */ +/* alert button +chat item action */ "Save" = "Guardar"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Guardar (y notificar contactos)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Guardar y notificar contacto"; /* No comment provided by engineer. */ @@ -3733,22 +4345,19 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Guardar y actualizar perfil del grupo"; -/* No comment provided by engineer. */ -"Save archive" = "Guardar archivo"; - -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Guardar configuración de auto aceptar"; - /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; +/* No comment provided by engineer. */ +"Save list" = "Guardar lista"; + /* No comment provided by engineer. */ "Save passphrase and open chat" = "Guardar contraseña y abrir el chat"; /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Guardar la contraseña en Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "¿Guardar preferencias?"; /* No comment provided by engineer. */ @@ -3757,15 +4366,15 @@ /* No comment provided by engineer. */ "Save servers" = "Guardar servidores"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "¿Guardar servidores?"; -/* No comment provided by engineer. */ -"Save settings?" = "¿Guardar configuración?"; - /* No comment provided by engineer. */ "Save welcome message?" = "¿Guardar mensaje de bienvenida?"; +/* alert title */ +"Save your profile?" = "¿Guardar tu perfil?"; + /* No comment provided by engineer. */ "saved" = "guardado"; @@ -3784,6 +4393,9 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Los servidores WebRTC ICE guardados serán eliminados"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Guardando %lld mensajes"; + /* No comment provided by engineer. */ "Scale" = "Escala"; @@ -3803,7 +4415,7 @@ "Scan security code from your contact's app." = "Escanea el código de seguridad desde la aplicación de tu contacto."; /* No comment provided by engineer. */ -"Scan server QR code" = "Escanear código QR del servidor"; +"Scan server QR code" = "Escanear código QR"; /* No comment provided by engineer. */ "search" = "buscar"; @@ -3847,6 +4459,9 @@ /* chat item action */ "Select" = "Seleccionar"; +/* No comment provided by engineer. */ +"Select chat profile" = "Selecciona perfil de chat"; + /* No comment provided by engineer. */ "Selected %lld" = "Seleccionados %lld"; @@ -3878,7 +4493,7 @@ "send direct message" = "Enviar mensaje directo"; /* No comment provided by engineer. */ -"Send direct message to connect" = "Envia un mensaje para conectar"; +"Send direct message to connect" = "Envía un mensaje para conectar"; /* No comment provided by engineer. */ "Send disappearing message" = "Enviar mensaje temporal"; @@ -3905,7 +4520,7 @@ "Send notifications" = "Enviar notificaciones"; /* No comment provided by engineer. */ -"Send notifications:" = "Enviar notificaciones:"; +"Send private reports" = "Envía informes privados"; /* No comment provided by engineer. */ "Send questions and ideas" = "Consultas y sugerencias"; @@ -3919,7 +4534,7 @@ /* 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. */ +/* alert message */ "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; /* No comment provided by engineer. */ @@ -3979,6 +4594,12 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Mediante proxy"; +/* No comment provided by engineer. */ +"Server" = "Servidor"; + +/* alert message */ +"Server added to operator %@." = "Servidor añadido al operador %@."; + /* No comment provided by engineer. */ "Server address" = "Dirección del servidor"; @@ -3988,6 +4609,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "La dirección del servidor es incompatible con la configuración de la red."; +/* alert title */ +"Server operator changed." = "El operador del servidor ha cambiado."; + +/* No comment provided by engineer. */ +"Server operators" = "Operadores de servidores"; + +/* alert title */ +"Server protocol changed." = "El protocolo del servidor ha cambiado."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "información cola del servidor: %1$@\n\núltimo mensaje recibido: %2$@"; @@ -3998,7 +4628,7 @@ "Server requires authorization to upload, check password" = "El servidor requiere autorización para subir, comprueba la contraseña"; /* No comment provided by engineer. */ -"Server test failed!" = "¡Error en prueba del servidor!"; +"Server test failed!" = "¡Prueba no superada!"; /* No comment provided by engineer. */ "Server type" = "Tipo de servidor"; @@ -4016,7 +4646,7 @@ "Servers info" = "Info servidores"; /* No comment provided by engineer. */ -"Servers statistics will be reset - this cannot be undone!" = "Las estadísticas de los servidores serán restablecidas. ¡No podrá deshacerse!"; +"Servers statistics will be reset - this cannot be undone!" = "Las estadísticas de los servidores serán restablecidas. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Session code" = "Código de sesión"; @@ -4024,6 +4654,9 @@ /* No comment provided by engineer. */ "Set 1 day" = "Establecer 1 día"; +/* No comment provided by engineer. */ +"Set chat name…" = "Nombre para el chat…"; + /* No comment provided by engineer. */ "Set contact name…" = "Escribe el nombre del contacto…"; @@ -4036,11 +4669,14 @@ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Úsalo en lugar de la autenticación del sistema."; +/* No comment provided by engineer. */ +"Set message expiration in chats." = "Establece el vencimiento para los mensajes en los chats."; + /* profile update event chat item */ "set new contact address" = "nueva dirección de contacto"; /* profile update event chat item */ -"set new profile picture" = "nueva imagen de perfil"; +"set new profile picture" = "tiene nueva imagen del perfil"; /* No comment provided by engineer. */ "Set passcode" = "Código autodestrucción"; @@ -4060,19 +4696,29 @@ /* No comment provided by engineer. */ "Settings" = "Configuración"; +/* alert message */ +"Settings were changed." = "La configuración ha sido modificada."; + /* No comment provided by engineer. */ "Shape profile images" = "Dar forma a las imágenes de perfil"; -/* chat item action */ +/* alert action +chat item action */ "Share" = "Compartir"; /* No comment provided by engineer. */ "Share 1-time link" = "Compartir enlace de un uso"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Compartir enlace de un uso con un amigo"; + /* No comment provided by engineer. */ "Share address" = "Compartir dirección"; /* No comment provided by engineer. */ +"Share address publicly" = "Campartir dirección públicamente"; + +/* alert title */ "Share address with contacts?" = "¿Compartir la dirección con los contactos?"; /* No comment provided by engineer. */ @@ -4081,6 +4727,12 @@ /* No comment provided by engineer. */ "Share link" = "Compartir enlace"; +/* No comment provided by engineer. */ +"Share profile" = "Perfil a compartir"; + +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Comparte tu dirección SimpleX en redes sociales."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Comparte este enlace de un solo uso"; @@ -4090,6 +4742,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Compartir con contactos"; +/* No comment provided by engineer. */ +"Short link" = "Enlace corto"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Mostrar → en mensajes con enrutamiento privado."; @@ -4126,6 +4781,18 @@ /* No comment provided by engineer. */ "SimpleX Address" = "Dirección SimpleX"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "Compartir enlaces de un solo uso y direcciones SimpleX es seguro a través de cualquier medio."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "¿Dirección SimpleX o enlace de un uso?"; + +/* simplex link type */ +"SimpleX channel link" = "Enlace de canal SimpleX"; + +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "Simplex Chat y Flux han acordado incluir en la aplicación servidores operados por Flux."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "La seguridad de SimpleX Chat ha sido auditada por Trail of Bits."; @@ -4142,7 +4809,7 @@ "SimpleX links" = "Enlaces SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "Los enlaces SimpleX no se permiten en este grupo."; +"SimpleX links are prohibited." = "Los enlaces SimpleX no se permiten en este grupo."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Enlaces SimpleX no permitidos"; @@ -4162,6 +4829,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Invitación SimpleX de un uso"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Protocolos de SimpleX auditados por Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modo incógnito simplificado"; @@ -4175,14 +4845,20 @@ "Skipped messages" = "Mensajes omitidos"; /* No comment provided by engineer. */ -"Small groups (max 20)" = "Grupos pequeños (máx. 20)"; +"Small groups (max 20)" = "Grupos pequeños (max. 20)"; /* No comment provided by engineer. */ "SMP server" = "Servidor SMP"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "Proxy SOCKS"; + /* blur media */ "Soft" = "Suave"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Algunas configuraciones de la app no han sido migradas."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Algunos archivos no han sido exportados:"; @@ -4192,9 +4868,16 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Han ocurrido algunos errores no críticos durante la importación:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Algunos servidores no han superado la prueba:\n%@"; + /* notification title */ "Somebody" = "Alguien"; +/* blocking reason +report reason */ +"Spam" = "Spam"; + /* No comment provided by engineer. */ "Square, circle, or anything in between." = "Cuadrada, circular o cualquier forma intermedia."; @@ -4226,10 +4909,7 @@ "Stop chat" = "Parar SimpleX"; /* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Para habilitar las acciones sobre la base de datos, debes parar SimpleX"; - -/* No comment provided by engineer. */ -"Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Para poder exportar, importar o eliminar la base de datos primero debes parar SimpleX. Mientras tanto no podrás recibir ni enviar mensajes."; +"Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Para exportar, importar o eliminar la base de datos debes parar SimpleX. Mientra tanto no podrás enviar ni recibir mensajes."; /* No comment provided by engineer. */ "Stop chat?" = "¿Parar Chat?"; @@ -4243,10 +4923,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "¿Dejar de enviar el archivo?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Dejar de compartir"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "¿Dejar de compartir la dirección?"; /* authentication reason */ @@ -4255,6 +4935,9 @@ /* No comment provided by engineer. */ "Stopping chat" = "Parando chat"; +/* No comment provided by engineer. */ +"Storage" = "Almacenamiento"; + /* No comment provided by engineer. */ "strike" = "tachado"; @@ -4265,7 +4948,7 @@ "Submit" = "Enviar"; /* No comment provided by engineer. */ -"Subscribed" = "Suscrito"; +"Subscribed" = "Suscritas"; /* No comment provided by engineer. */ "Subscription errors" = "Errores de suscripción"; @@ -4276,18 +4959,30 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Soporte SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Intercambia audio y video durante la llamada."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Cambia el perfil de chat para invitaciones de un solo uso."; + /* No comment provided by engineer. */ "System" = "Sistema"; /* No comment provided by engineer. */ "System authentication" = "Autenticación del sistema"; +/* No comment provided by engineer. */ +"Tail" = "Cola"; + /* No comment provided by engineer. */ "Take picture" = "Tomar foto"; /* No comment provided by engineer. */ "Tap button " = "Pulsa el botón "; +/* 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 to activate profile." = "Pulsa sobre un perfil para activarlo."; @@ -4312,6 +5007,9 @@ /* No comment provided by engineer. */ "TCP connection timeout" = "Timeout de la conexión TCP"; +/* No comment provided by engineer. */ +"TCP port for messaging" = "Puerto TCP para mensajes"; + /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -4321,11 +5019,14 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; -/* No comment provided by engineer. */ +/* file error alert title */ "Temporary file error" = "Error en archivo temporal"; /* server test failure */ -"Test failed at step %@." = "La prueba ha fallado en el paso %@."; +"Test failed at step %@." = "Prueba no superada en el paso %@."; + +/* No comment provided by engineer. */ +"Test notifications" = "Probar notificaciones"; /* No comment provided by engineer. */ "Test server" = "Probar servidor"; @@ -4333,8 +5034,8 @@ /* No comment provided by engineer. */ "Test servers" = "Probar servidores"; -/* No comment provided by engineer. */ -"Tests failed!" = "¡Pruebas fallidas!"; +/* alert title */ +"Tests failed!" = "¡Pruebas no superadas!"; /* No comment provided by engineer. */ "Thank you for installing SimpleX Chat!" = "¡Gracias por instalar SimpleX Chat!"; @@ -4346,10 +5047,10 @@ "Thanks to the users – contribute via Weblate!" = "¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "La primera plataforma sin identificadores de usuario: diseñada para la privacidad."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; +"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 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)."; @@ -4358,7 +5059,10 @@ "The attempt to change database passphrase was not completed." = "El intento de cambiar la contraseña de la base de datos no se ha completado."; /* 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 SimpleX."; +"The code you scanned is not a SimpleX link QR code." = "El código QR escaneado no es un enlace de SimpleX."; + +/* 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."; /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "¡La conexión que has aceptado se cancelará!"; @@ -4372,6 +5076,9 @@ /* No comment provided by engineer. */ "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"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "El hash del mensaje anterior es diferente."; @@ -4390,14 +5097,17 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Los mensajes serán marcados como moderados para todos los miembros."; -/* No comment provided by engineer. */ -"The next generation of private messaging" = "La nueva generación de mensajería privada"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; +"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; + +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "¡Segundo operador predefinido!"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "¡El doble check que nos faltaba! ✅"; @@ -4406,14 +5116,23 @@ "The sender will NOT be notified" = "El remitente NO será notificado"; /* No comment provided by engineer. */ -"The servers for new connections of your current chat profile **%@**." = "Lista de servidores para las conexiones nuevas de tu perfil actual **%@**."; +"The servers for new connections of your current chat profile **%@**." = "Servidores para conexiones nuevas en tu perfil **%@**."; /* No comment provided by engineer. */ -"The text you pasted is not a SimpleX link." = "El texto pegado no es un enlace SimpleX."; +"The servers for new files of your current chat profile **%@**." = "Servidores para enviar archivos en tu perfil **%@**."; + +/* No comment provided by engineer. */ +"The text you pasted is not a SimpleX link." = "El texto pegado no es un enlace de SimpleX."; + +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "El archivo de bases de datos subido será eliminado permanentemente de los servidores."; /* No comment provided by engineer. */ "Themes" = "Temas"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Estas condiciones también se aplican para: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Esta configuración afecta a tu perfil actual **%@**."; @@ -4426,6 +5145,9 @@ /* 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." = "Esta acción es irreversible. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Podría tardar varios minutos."; +/* alert message */ +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No puede deshacerse!"; + /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente."; @@ -4456,9 +5178,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "¡Esta es tu propia dirección SimpleX!"; +/* 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."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador."; +/* No comment provided by engineer. */ +"This message was deleted or not received yet." = "El mensaje ha sido eliminado o aún no se ha recibido."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Esta configuración se aplica a los mensajes del perfil actual **%@**."; @@ -4478,7 +5206,7 @@ "To make a new connection" = "Para hacer una conexión nueva"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; +"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."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC."; @@ -4489,15 +5217,33 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, SimpleX usa identificadores distintos para cada uno de tus contactos."; + +/* No comment provided by engineer. */ +"To receive" = "Para recibir"; + +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Para grabación de voz, por favor concede el permiso para usar el micrófono."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Para grabación de vídeo, por favor concede el permiso para usar la cámara."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Para grabar el mensaje de voz concede permiso para usar el micrófono."; /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Para hacer visible tu perfil oculto, introduce la contraseña en el campo de búsqueda del menú **Mis perfiles**."; +/* No comment provided by engineer. */ +"To send" = "Para enviar"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Para usar los servidores de **%@**, debes aceptar las condiciones de uso."; + /* 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."; @@ -4507,6 +5253,9 @@ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Activa incógnito al conectar."; +/* token status */ +"Token status: %@." = "Estado token: %@."; + /* No comment provided by engineer. */ "Toolbar opacity" = "Opacidad barra"; @@ -4547,7 +5296,7 @@ "Unblock member" = "Desbloquear miembro"; /* No comment provided by engineer. */ -"Unblock member for all?" = "¿Desbloquear miembro para todos?"; +"Unblock member for all?" = "¿Desbloquear el miembro para todos?"; /* No comment provided by engineer. */ "Unblock member?" = "¿Desbloquear miembro?"; @@ -4555,6 +5304,9 @@ /* rcv group event chat item */ "unblocked %@" = "ha desbloqueado a %@"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Mensajes no entregados"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Estado de migración inesperado"; @@ -4588,7 +5340,7 @@ /* No comment provided by engineer. */ "unknown servers" = "con servidores desconocidos"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "¡Servidores desconocidos!"; /* No comment provided by engineer. */ @@ -4598,7 +5350,7 @@ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "A menos que utilices la interfaz de llamadas de iOS, activa el modo No molestar para evitar interrupciones."; /* 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." = "A menos que tu contacto haya eliminado la conexión o el enlace haya sido usado, podría ser un error. Por favor, notifícalo.\nPara conectarte pide a tu contacto que cree otro enlace y comprueba la conexión de red."; +"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." = "A menos que tu contacto haya eliminado la conexión o el enlace se haya usado, podría ser un error. Por favor, notifícalo.\nPara conectarte pide a tu contacto que cree otro enlace y comprueba la conexión de red."; /* No comment provided by engineer. */ "Unlink" = "Desenlazar"; @@ -4612,10 +5364,7 @@ /* authentication reason */ "Unlock app" = "Desbloquear aplicación"; -/* No comment provided by engineer. */ -"unmute" = "activar sonido"; - -/* swipe action */ +/* notification label action */ "Unmute" = "Activar audio"; /* No comment provided by engineer. */ @@ -4624,6 +5373,9 @@ /* swipe action */ "Unread" = "No leído"; +/* No comment provided by engineer. */ +"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."; @@ -4639,6 +5391,9 @@ /* No comment provided by engineer. */ "Update settings?" = "¿Actualizar configuración?"; +/* No comment provided by engineer. */ +"Updated conditions" = "Condiciones actualizadas"; + /* rcv group event chat item */ "updated group profile" = "ha actualizado el perfil del grupo"; @@ -4646,7 +5401,7 @@ "updated profile" = "perfil actualizado"; /* No comment provided by engineer. */ -"Updating settings will re-connect the client to all servers." = "Al actualizar la configuración el cliente se reconectará a todos los servidores."; +"Updating settings will re-connect the client to all servers." = "Para actualizar la configuración el cliente se reconectará a todos los servidores."; /* No comment provided by engineer. */ "Upgrade and open chat" = "Actualizar y abrir Chat"; @@ -4672,6 +5427,9 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Usar hosts .onion"; +/* No comment provided by engineer. */ +"Use %@" = "Usar %@"; + /* No comment provided by engineer. */ "Use chat" = "Usar Chat"; @@ -4679,7 +5437,13 @@ "Use current profile" = "Usar perfil actual"; /* No comment provided by engineer. */ -"Use for new connections" = "Usar para conexiones nuevas"; +"Use for files" = "Uso para archivos"; + +/* No comment provided by engineer. */ +"Use for messages" = "Uso para mensajes"; + +/* No comment provided by engineer. */ +"Use for new connections" = "Para conexiones nuevas"; /* No comment provided by engineer. */ "Use from desktop" = "Usar desde ordenador"; @@ -4697,14 +5461,29 @@ "Use private routing with unknown servers when IP address is not protected." = "Usar enrutamiento privado con servidores desconocidos cuando tu dirección IP no está protegida."; /* No comment provided by engineer. */ -"Use private routing with unknown servers." = "Usar enrutamiento privado con servidores de retransmisión desconocidos."; +"Use private routing with unknown servers." = "Usar enrutamiento privado con servidores de mensaje desconocidos."; /* No comment provided by engineer. */ "Use server" = "Usar servidor"; +/* No comment provided by engineer. */ +"Use servers" = "Usar servidores"; + +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Usar enlaces cortos (BETA)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "¿Usar servidores SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "Usar proxy SOCKS"; + +/* No comment provided by engineer. */ +"Use TCP port %@ when no port is specified." = "Se usa el puerto TCP %@ cuando no se ha especificado otro."; + +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Usar puerto TCP 443 solo en servidores predefinidos."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Usar la aplicación durante la llamada."; @@ -4712,11 +5491,14 @@ "Use the app with one hand." = "Usa la aplicación con una sola mano."; /* No comment provided by engineer. */ -"User profile" = "Perfil de usuario"; +"Use web port" = "Usar puerto web"; /* No comment provided by engineer. */ "User selection" = "Selección de usuarios"; +/* No comment provided by engineer. */ +"Username" = "Nombre de usuario"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Usar servidores SimpleX Chat."; @@ -4783,9 +5565,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Vídeos y archivos de hasta 1Gb"; +/* No comment provided by engineer. */ +"View conditions" = "Ver condiciones"; + /* No comment provided by engineer. */ "View security code" = "Mostrar código de seguridad"; +/* No comment provided by engineer. */ +"View updated conditions" = "Ver condiciones actualizadas"; + /* chat feature */ "Visible history" = "Historial visible"; @@ -4799,7 +5587,7 @@ "Voice messages are prohibited in this chat." = "Los mensajes de voz no están permitidos en este chat."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Los mensajes de voz no están permitidos en este grupo."; +"Voice messages are prohibited." = "Los mensajes de voz no están permitidos en este grupo."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Mensajes de voz no permitidos"; @@ -4868,7 +5656,7 @@ "when IP hidden" = "con IP oculta"; /* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Cuando alguien solicite conectarse podrás aceptar o rechazar la solicitud."; +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién."; /* 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."; @@ -4894,7 +5682,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Sin Tor o VPN, tu dirección IP será visible para los servidores de archivos."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sin Tor o VPN, tu dirección IP será visible para estos servidores XFTP: %@."; /* No comment provided by engineer. */ @@ -4918,9 +5706,6 @@ /* No comment provided by engineer. */ "you" = "tu"; -/* No comment provided by engineer. */ -"You" = "Tú"; - /* No comment provided by engineer. */ "You **must not** use the same database on two devices." = "**No debes** usar la misma base de datos en dos dispositivos."; @@ -4934,7 +5719,10 @@ "You already have a chat profile with the same display name. Please choose another name." = "Ya tienes un perfil con este nombre mostrado. Por favor, elige otro nombre."; /* No comment provided by engineer. */ -"You are already connected to %@." = "Ya estás conectado a %@."; +"You are already connected to %@." = "Ya estás conectado con %@."; + +/* No comment provided by engineer. */ +"You are already connected with %@." = "Ya estás conectado con %@."; /* No comment provided by engineer. */ "You are already connecting to %@." = "Ya estás conectando con %@."; @@ -4967,7 +5755,7 @@ "You are invited to group" = "Has sido invitado a un grupo"; /* No comment provided by engineer. */ -"You are not connected to these servers. Private routing is used to deliver messages to them." = "No estás conectado a estos servidores. Para enviarles mensajes se usa el enrutamiento privado."; +"You are not connected to these servers. Private routing is used to deliver messages to them." = "No tienes conexión directa a estos servidores. Los mensajes destinados a estos usan enrutamiento privado."; /* No comment provided by engineer. */ "you are observer" = "Tu rol es observador"; @@ -4981,6 +5769,9 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Puedes cambiar la posición de la barra desde el menú Apariencia."; +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Puedes configurar los servidores a través de su configuración."; + /* No comment provided by engineer. */ "You can create it later" = "Puedes crearla más tarde"; @@ -5005,6 +5796,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Puedes enviar mensajes a %@ desde Contactos archivados."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Puedes añadir un nombre a la conexión para recordar a quién corresponde."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración."; @@ -5014,9 +5808,6 @@ /* 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 **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Puedes compartir tu dirección como enlace o código QR para que cualquiera pueda conectarse contigo."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Puede iniciar Chat a través de la Configuración / Base de datos de la aplicación o reiniciando la aplicación"; @@ -5029,7 +5820,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Puedes usar la sintaxis markdown para dar formato a tus mensajes:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Podrás ver el enlace de invitación en detalles de conexión."; /* No comment provided by engineer. */ @@ -5048,10 +5839,10 @@ "you changed role of %@ to %@" = "has cambiado el rol de %1$@ a %2$@"; /* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Tú controlas a través de qué servidor(es) **recibes** los mensajes. Tus contactos controlan a través de qué servidor(es) **envías** tus mensajes."; +"You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; +"You decide who can connect." = "Tu decides quién se conecta."; /* No comment provided by engineer. */ "You have already requested connection via this address!" = "¡Ya has solicitado la conexión mediante esta dirección!"; @@ -5084,7 +5875,7 @@ "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." = "Debes usar la versión más reciente de tu base de datos ÚNICAMENTE en un dispositivo, de lo contrario podrías dejar de recibir mensajes de algunos contactos."; /* No comment provided by engineer. */ -"You need to allow your contact to call to be able to call them." = "Necesitas permitir que tus contacto llamen para poder llamarles."; +"You need to allow your contact to call to be able to call them." = "Debes permitir que tus contacto te llamen para poder llamarles."; /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "Para poder enviar mensajes de voz antes debes permitir que tu contacto pueda enviarlos."; @@ -5104,6 +5895,9 @@ /* chat list item description */ "you shared one-time link incognito" = "has compartido enlace de un solo uso en modo incógnito"; +/* token info */ +"You should receive notifications." = "Deberías recibir notificaciones."; + /* snd group event chat item */ "you unblocked %@" = "has desbloqueado a %@"; @@ -5128,6 +5922,9 @@ /* No comment provided by engineer. */ "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 de este chat. El historial del chat se conserva."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Dejarás de recibir mensajes de este grupo. El historial del chat se conservará."; @@ -5143,9 +5940,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Estás usando un perfil incógnito en este grupo. Para evitar descubrir tu perfil principal no se permite invitar contactos"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Mis servidores %@"; - /* No comment provided by engineer. */ "Your calls" = "Llamadas"; @@ -5155,9 +5949,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "La base de datos no está cifrada - establece una contraseña para cifrarla."; +/* alert title */ +"Your chat preferences" = "Tus preferencias de chat"; + /* No comment provided by engineer. */ "Your chat profiles" = "Mis perfiles"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Tu conexión ha sido trasladada a %@ pero ha ocurrido un error inesperado al redirigirte al perfil."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "El contacto ha enviado un archivo mayor al máximo admitido (%@)."; @@ -5167,6 +5967,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Tus contactos permanecerán conectados."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Tus credenciales podrían ser enviadas sin cifrar."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "La base de datos actual será ELIMINADA y SUSTITUIDA por la importada."; @@ -5189,7 +5992,10 @@ "Your profile **%@** will be shared." = "El perfil **%@** será compartido."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos.\nLos servidores SimpleX no pueden ver tu perfil."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil."; + +/* 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 profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo."; @@ -5198,10 +6004,10 @@ "Your random profile" = "Tu perfil aleatorio"; /* No comment provided by engineer. */ -"Your server" = "Tu servidor"; +"Your server address" = "Dirección del servidor"; /* No comment provided by engineer. */ -"Your server address" = "Dirección de tu servidor"; +"Your servers" = "Tus servidores"; /* No comment provided by engineer. */ "Your settings" = "Configuración"; @@ -5209,9 +6015,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Mi dirección SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Servidores SMP"; - -/* No comment provided by engineer. */ -"Your XFTP servers" = "Servidores XFTP"; - diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index a7820e69b1..4891c7fb26 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (voidaan kopioida)"; @@ -25,24 +10,9 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- ääniviestit enintään 5 minuuttia.\n- mukautettu katoamisaika.\n- historian muokkaaminen."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 värillinen!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Osallistu](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -52,9 +22,6 @@ /* 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. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille."; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e-salattu** äänipuhelu"; @@ -62,16 +29,16 @@ "**e2e encrypted** video call" = "**e2e-salattu** videopuhelu"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Yksityisempi**: tarkista uudet viestit 20 minuutin välein. Laitetunnus jaetaan SimpleX Chat -palvelimen kanssa, mutta ei sitä, kuinka monta yhteystietoa tai viestiä sinulla on."; +"**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." = "**Yksityisempi**: tarkista uudet viestit 20 minuutin välein. Laitetunnus jaetaan SimpleX Chat -palvelimen kanssa, mutta ei sitä, kuinka monta yhteystietoa tai viestiä sinulla on."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta)."; +"**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." = "**Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta)."; /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin."; +"**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." = "**Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin."; @@ -172,9 +139,6 @@ /* No comment provided by engineer. */ "%lld new interface languages" = "%lld uutta käyttöliittymän kieltä"; -/* No comment provided by engineer. */ -"%lld second(s)" = "%lld sekunti(a)"; - /* No comment provided by engineer. */ "%lld seconds" = "%lld sekuntia"; @@ -217,7 +181,8 @@ /* No comment provided by engineer. */ "0s" = "0s"; -/* time interval */ +/* delete after time +time interval */ "1 day" = "1 päivä"; /* time interval */ @@ -226,10 +191,12 @@ /* No comment provided by engineer. */ "1 minute" = "1 minuutti"; -/* time interval */ +/* delete after time +time interval */ "1 month" = "1 kuukausi"; -/* time interval */ +/* delete after time +time interval */ "1 week" = "1 viikko"; /* No comment provided by engineer. */ @@ -265,12 +232,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Keskeytä osoitteenvaihto?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Tietoja SimpleX:stä"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Tietoja SimpleX osoitteesta"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Tietoja SimpleX Chatistä"; @@ -278,8 +239,8 @@ "above, then choose:" = "edellä, valitse sitten:"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Hyväksy"; /* No comment provided by engineer. */ @@ -289,7 +250,7 @@ "Accept contact request from %@?" = "Hyväksy kontaktipyyntö %@:ltä?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Hyväksy tuntematon"; /* call status */ @@ -298,9 +259,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi."; -/* No comment provided by engineer. */ -"Add preset servers" = "Lisää esiasetettuja palvelimia"; - /* No comment provided by engineer. */ "Add profile" = "Lisää profiili"; @@ -427,6 +385,9 @@ /* 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: %@"; @@ -547,7 +508,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Kontakteja ei voi kutsua!"; -/* No comment provided by engineer. */ +/* alert action +alert button */ "Cancel" = "Peruuta"; /* feature offered item */ @@ -556,7 +518,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Ei pääsyä avainnippuun tietokannan salasanan tallentamiseksi"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Tiedostoa ei voi vastaanottaa"; /* No comment provided by engineer. */ @@ -587,7 +549,7 @@ "Change self-destruct mode" = "Vaihda itsetuhotilaa"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Vaihda itsetuhoutuva pääsykoodi"; /* chat item text */ @@ -605,9 +567,6 @@ /* chat item text */ "changing address…" = "muuttamassa osoitetta…"; -/* No comment provided by engineer. */ -"Chat archive" = "Chat-arkisto"; - /* No comment provided by engineer. */ "Chat console" = "Chat-konsoli"; @@ -630,9 +589,12 @@ "Chat preferences" = "Chat-asetukset"; /* No comment provided by engineer. */ -"Chats" = "Keskustelut"; +"Chat profile" = "Käyttäjäprofiili"; /* No comment provided by engineer. */ +"Chats" = "Keskustelut"; + +/* alert title */ "Check server address and try again." = "Tarkista palvelimen osoite ja yritä uudelleen."; /* No comment provided by engineer. */ @@ -728,7 +690,7 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Yhteyden muodostaminen palvelimeen... (virhe: %@)"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "yhdistää…"; /* No comment provided by engineer. */ @@ -794,9 +756,6 @@ /* No comment provided by engineer. */ "Create" = "Luo"; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Luo osoite, jolla ihmiset voivat ottaa sinuun yhteyttä."; - /* server test step */ "Create file" = "Luo tiedosto"; @@ -809,6 +768,9 @@ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻"; +/* No comment provided by engineer. */ +"Create profile" = "Luo profiilisi"; + /* server test step */ "Create queue" = "Luo jono"; @@ -821,9 +783,6 @@ /* No comment provided by engineer. */ "Create your profile" = "Luo profiilisi"; -/* No comment provided by engineer. */ -"Created on %@" = "Luotu %@"; - /* No comment provided by engineer. */ "creator" = "luoja"; @@ -911,7 +870,8 @@ /* message decrypt error item */ "Decryption error" = "Salauksen purkuvirhe"; -/* pref value */ +/* delete after time +pref value */ "default (%@)" = "oletusarvo (%@)"; /* No comment provided by engineer. */ @@ -920,8 +880,8 @@ /* No comment provided by engineer. */ "default (yes)" = "oletusarvo (kyllä)"; -/* chat item action - swipe action */ +/* alert action +swipe action */ "Delete" = "Poista"; /* No comment provided by engineer. */ @@ -936,12 +896,6 @@ /* No comment provided by engineer. */ "Delete all files" = "Poista kaikki tiedostot"; -/* No comment provided by engineer. */ -"Delete archive" = "Poista arkisto"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Poista keskusteluarkisto?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Poista keskusteluprofiili"; @@ -993,7 +947,7 @@ /* No comment provided by engineer. */ "Delete message?" = "Poista viesti?"; -/* No comment provided by engineer. */ +/* alert button */ "Delete messages" = "Poista viestit"; /* No comment provided by engineer. */ @@ -1069,7 +1023,7 @@ "Direct messages" = "Yksityisviestit"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä."; +"Direct messages between members are prohibited." = "Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Poista käytöstä (pidä ohitukset)"; @@ -1093,7 +1047,7 @@ "Disappearing messages are prohibited in this chat." = "Katoavat viestit ovat kiellettyjä tässä keskustelussa."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Katoavat viestit ovat kiellettyjä tässä ryhmässä."; +"Disappearing messages are prohibited." = "Katoavat viestit ovat kiellettyjä tässä ryhmässä."; /* No comment provided by engineer. */ "Disappears at" = "Katoaa klo"; @@ -1152,7 +1106,7 @@ /* No comment provided by engineer. */ "Enable (keep overrides)" = "Salli (pidä ohitukset)"; -/* No comment provided by engineer. */ +/* alert title */ "Enable automatic message deletion?" = "Ota automaattinen viestien poisto käyttöön?"; /* No comment provided by engineer. */ @@ -1284,9 +1238,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Virhe kontaktipyynnön hyväksymisessä"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Virhe tietokantatiedoston käyttämisessä"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Virhe lisättäessä jäseniä"; @@ -1353,18 +1304,12 @@ /* No comment provided by engineer. */ "Error joining group" = "Virhe ryhmään liittymisessä"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Virhe %@-palvelimien lataamisessa"; - -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Virhe tiedoston vastaanottamisessa"; /* No comment provided by engineer. */ "Error removing member" = "Virhe poistettaessa jäsentä"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Virhe %@ palvelimien tallentamisessa"; - /* No comment provided by engineer. */ "Error saving group profile" = "Virhe ryhmäprofiilin tallentamisessa"; @@ -1395,7 +1340,7 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Virhe keskustelun lopettamisessa"; -/* No comment provided by engineer. */ +/* alertTitle */ "Error switching profile!" = "Virhe profiilin vaihdossa!"; /* No comment provided by engineer. */ @@ -1416,8 +1361,9 @@ /* No comment provided by engineer. */ "Error: " = "Virhe: "; -/* file error text - snd error text */ +/* alert message +file error text +snd error text */ "Error: %@" = "Virhe: %@"; /* No comment provided by engineer. */ @@ -1429,9 +1375,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Jopa kun ei käytössä keskustelussa."; -/* No comment provided by engineer. */ -"event happened" = "tapahtuma tapahtui"; - /* No comment provided by engineer. */ "Exit without saving" = "Poistu tallentamatta"; @@ -1475,7 +1418,7 @@ "Files and media" = "Tiedostot ja media"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Tiedostot ja media ovat tässä ryhmässä kiellettyjä."; +"Files and media are prohibited." = "Tiedostot ja media ovat tässä ryhmässä kiellettyjä."; /* No comment provided by engineer. */ "Files and media prohibited!" = "Tiedostot ja media kielletty!"; @@ -1519,9 +1462,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Koko nimi (valinnainen)"; -/* No comment provided by engineer. */ -"Full name:" = "Koko nimi:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Täysin uudistettu - toimii taustalla!"; @@ -1561,24 +1501,6 @@ /* No comment provided by engineer. */ "Group links" = "Ryhmälinkit"; -/* No comment provided by engineer. */ -"Group members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita."; - -/* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)"; - -/* No comment provided by engineer. */ -"Group members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä."; - -/* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Ryhmän jäsenet voivat lähettää katoavia viestejä."; - -/* No comment provided by engineer. */ -"Group members can send files and media." = "Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa."; - -/* No comment provided by engineer. */ -"Group members can send voice messages." = "Ryhmän jäsenet voivat lähettää ääniviestejä."; - /* notification */ "Group message:" = "Ryhmäviesti:"; @@ -1636,9 +1558,6 @@ /* time unit */ "hours" = "tuntia"; -/* No comment provided by engineer. */ -"How it works" = "Kuinka se toimii"; - /* No comment provided by engineer. */ "How SimpleX works" = "Miten SimpleX toimii"; @@ -1679,7 +1598,7 @@ "Immediately" = "Heti"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Immuuni roskapostille ja väärinkäytöksille"; +"Immune to spam" = "Immuuni roskapostille ja väärinkäytöksille"; /* No comment provided by engineer. */ "Import" = "Tuo"; @@ -1748,10 +1667,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Välittömät push-ilmoitukset ovat piilossa!\n"; +"Instant" = "Heti"; /* No comment provided by engineer. */ -"Instantly" = "Heti"; +"Instant push notifications will be hidden!\n" = "Välittömät push-ilmoitukset ovat piilossa!\n"; /* No comment provided by engineer. */ "Interface" = "Käyttöliittymä"; @@ -1768,7 +1687,7 @@ /* invalid chat item */ "invalid data" = "virheelliset tiedot"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Virheellinen palvelinosoite!"; /* item status text */ @@ -1814,7 +1733,7 @@ "Irreversible message deletion is prohibited in this chat." = "Viestien peruuttamaton poisto on kielletty tässä keskustelussa."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Viestien peruuttamaton poisto on kielletty tässä ryhmässä."; +"Irreversible message deletion is prohibited." = "Viestien peruuttamaton poisto on kielletty tässä ryhmässä."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Se mahdollistaa useiden nimettömien yhteyksien muodostamisen yhdessä keskusteluprofiilissa ilman, että niiden välillä on jaettuja tietoja."; @@ -1897,9 +1816,6 @@ /* No comment provided by engineer. */ "Live messages" = "Live-viestit"; -/* No comment provided by engineer. */ -"Local" = "Paikallinen"; - /* No comment provided by engineer. */ "Local name" = "Paikallinen nimi"; @@ -1912,24 +1828,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Lukitustila"; -/* No comment provided by engineer. */ -"Make a private connection" = "Luo yksityinen yhteys"; - /* No comment provided by engineer. */ "Make one message disappear" = "Hävitä yksi viesti"; /* No comment provided by engineer. */ "Make profile private!" = "Tee profiilista yksityinen!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Varmista, että %@-palvelinosoitteet ovat oikeassa muodossa, että ne on erotettu toisistaan riveittäin ja että ne eivät ole päällekkäisiä (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Varmista, että WebRTC ICE -palvelinosoitteet ovat oikeassa muodossa, rivieroteltuina ja että ne eivät ole päällekkäisiä."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Monet ihmiset kysyivät: *Jos SimpleX:llä ei ole käyttäjätunnuksia, miten se voi toimittaa viestejä?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Merkitse poistetuksi kaikilta"; @@ -1966,6 +1873,24 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Jäsen poistetaan ryhmästä - tätä ei voi perua!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita."; + +/* 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)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Ryhmän jäsenet voivat lähettää katoavia viestejä."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Ryhmän jäsenet voivat lähettää ääniviestejä."; + /* item status text */ "Message delivery error" = "Viestin toimitusvirhe"; @@ -1982,7 +1907,7 @@ "Message reactions are prohibited in this chat." = "Viestireaktiot ovat kiellettyjä tässä keskustelussa."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Viestireaktiot ovat kiellettyjä tässä ryhmässä."; +"Message reactions are prohibited." = "Viestireaktiot ovat kiellettyjä tässä ryhmässä."; /* notification */ "message received" = "viesti vastaanotettu"; @@ -2009,7 +1934,7 @@ "Migration is completed" = "Siirto on valmis"; /* No comment provided by engineer. */ -"Migrations: %@" = "Siirrot: %@"; +"Migrations:" = "Siirrot:"; /* time unit */ "minutes" = "minuuttia"; @@ -2044,7 +1969,7 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Useita keskusteluprofiileja"; -/* swipe action */ +/* notification label action */ "Mute" = "Mykistä"; /* No comment provided by engineer. */ @@ -2062,7 +1987,7 @@ /* No comment provided by engineer. */ "Network status" = "Verkon tila"; -/* No comment provided by engineer. */ +/* delete after time */ "never" = "ei koskaan"; /* notification */ @@ -2071,9 +1996,6 @@ /* notification */ "New contact:" = "Uusi kontakti:"; -/* No comment provided by engineer. */ -"New database archive" = "Uusi tietokanta-arkisto"; - /* No comment provided by engineer. */ "New display name" = "Uusi näyttönimi"; @@ -2131,12 +2053,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Ei lupaa ääniviestin tallentamiseen"; +/* No comment provided by engineer. */ +"No push server" = "Paikallinen"; + /* No comment provided by engineer. */ "No received or sent files" = "Ei vastaanotettuja tai lähetettyjä tiedostoja"; /* 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"; @@ -2150,8 +2078,8 @@ "observer" = "tarkkailija"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "pois"; /* blur media */ @@ -2163,15 +2091,12 @@ /* feature offered item */ "offered %@: %@" = "tarjottu %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ "Old database" = "Vanha tietokanta"; -/* No comment provided by engineer. */ -"Old database archive" = "Vanha tietokanta-arkisto"; - /* group pref value */ "on" = "päällä"; @@ -2188,7 +2113,7 @@ "Onion hosts will not be used." = "Onion-isäntiä ei käytetä."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**."; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Vain ryhmän omistajat voivat muuttaa ryhmän asetuksia."; @@ -2238,12 +2163,6 @@ /* No comment provided by engineer. */ "Open Settings" = "Avaa Asetukset"; -/* authentication reason */ -"Open user profiles" = "Avaa käyttäjäprofiilit"; - -/* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; - /* member role */ "owner" = "omistaja"; @@ -2272,10 +2191,7 @@ "peer-to-peer" = "vertais"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Ihmiset voivat ottaa sinuun yhteyttä vain jakamiesi linkkien kautta."; - -/* No comment provided by engineer. */ -"Periodically" = "Ajoittain"; +"Periodic" = "Ajoittain"; /* message decrypt error item */ "Permanent decryption error" = "Pysyvä salauksen purkuvirhe"; @@ -2331,9 +2247,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Säilytä viimeinen viestiluonnos liitteineen."; -/* No comment provided by engineer. */ -"Preset server" = "Esiasetettu palvelin"; - /* No comment provided by engineer. */ "Preset server address" = "Esiasetettu palvelimen osoite"; @@ -2358,7 +2271,7 @@ /* No comment provided by engineer. */ "Profile password" = "Profiilin salasana"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Profiilipäivitys lähetetään kontakteillesi."; /* No comment provided by engineer. */ @@ -2413,7 +2326,7 @@ "Read more" = "Lue lisää"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"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)."; @@ -2421,9 +2334,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Lue lisää GitHub-tietovarastostamme."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Kuittaukset pois käytöstä"; @@ -2473,7 +2383,7 @@ "Reduced battery usage" = "Pienempi akun käyttö"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Hylkää"; /* No comment provided by engineer. */ @@ -2572,13 +2482,14 @@ /* No comment provided by engineer. */ "Run chat" = "Käynnistä chat"; -/* chat item action */ +/* alert button +chat item action */ "Save" = "Tallenna"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Tallenna (ja ilmoita kontakteille)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Tallenna ja ilmoita kontaktille"; /* No comment provided by engineer. */ @@ -2587,12 +2498,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Tallenna ja päivitä ryhmäprofiili"; -/* No comment provided by engineer. */ -"Save archive" = "Tallenna arkisto"; - -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Tallenna automaattisen hyväksynnän asetukset"; - /* No comment provided by engineer. */ "Save group profile" = "Tallenna ryhmäprofiili"; @@ -2602,7 +2507,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Tallenna tunnuslause Avainnippuun"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Tallenna asetukset?"; /* No comment provided by engineer. */ @@ -2611,12 +2516,9 @@ /* No comment provided by engineer. */ "Save servers" = "Tallenna palvelimet"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Tallenna palvelimet?"; -/* No comment provided by engineer. */ -"Save settings?" = "Tallenna asetukset?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Tallenna tervetuloviesti?"; @@ -2695,9 +2597,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Lähetys ilmoitukset"; -/* No comment provided by engineer. */ -"Send notifications:" = "Lähetys ilmoitukset:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Lähetä kysymyksiä ja ideoita"; @@ -2707,7 +2606,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Lähetä ne galleriasta tai mukautetuista näppäimistöistä."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Lähettäjä peruutti tiedoston siirron."; /* No comment provided by engineer. */ @@ -2791,7 +2690,8 @@ /* No comment provided by engineer. */ "Settings" = "Asetukset"; -/* chat item action */ +/* alert action +chat item action */ "Share" = "Jaa"; /* No comment provided by engineer. */ @@ -2800,7 +2700,7 @@ /* No comment provided by engineer. */ "Share address" = "Jaa osoite"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Jaa osoite kontakteille?"; /* No comment provided by engineer. */ @@ -2887,9 +2787,6 @@ /* No comment provided by engineer. */ "Stop" = "Lopeta"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Pysäytä keskustelu tietokantatoimien mahdollistamiseksi"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Pysäytä keskustelut viedäksesi, tuodaksesi tai poistaaksesi keskustelujen tietokannan. Et voi vastaanottaa ja lähettää viestejä, kun keskustelut on pysäytetty."; @@ -2905,10 +2802,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Lopeta tiedoston lähettäminen?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Lopeta jakaminen"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Lopeta osoitteen jakaminen?"; /* authentication reason */ @@ -2965,7 +2862,7 @@ /* No comment provided by engineer. */ "Test servers" = "Testipalvelimet"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testit epäonnistuivat!"; /* No comment provided by engineer. */ @@ -2977,9 +2874,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Kiitokset käyttäjille – osallistu Weblaten kautta!"; -/* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön."; @@ -2998,6 +2892,9 @@ /* 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."; @@ -3010,14 +2907,11 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Viesti merkitään moderoiduksi kaikille jäsenille."; -/* No comment provided by engineer. */ -"The next generation of private messaging" = "Seuraavan sukupolven yksityisviestit"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; +"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Toinen kuittaus, joka uupui! ✅"; @@ -3064,15 +2958,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "Uuden yhteyden luominen"; -/* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Aikavyöhykkeen suojaamiseksi kuva-/äänitiedostot käyttävät UTC:tä."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Suojaa tietosi ottamalla SimpleX Lock käyttöön.\nSinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus otetaan käyttöön."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Jos haluat nauhoittaa ääniviestin, anna lupa käyttää mikrofonia."; @@ -3145,7 +3039,7 @@ /* authentication reason */ "Unlock app" = "Avaa sovellus"; -/* swipe action */ +/* notification label action */ "Unmute" = "Poista mykistys"; /* swipe action */ @@ -3196,9 +3090,6 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Käytä SimpleX Chat palvelimia?"; -/* No comment provided by engineer. */ -"User profile" = "Käyttäjäprofiili"; - /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Käyttää SimpleX Chat -palvelimia."; @@ -3254,7 +3145,7 @@ "Voice messages are prohibited in this chat." = "Ääniviestit ovat kiellettyjä tässä keskustelussa."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Ääniviestit ovat kiellettyjä tässä ryhmässä."; +"Voice messages are prohibited." = "Ääniviestit ovat kiellettyjä tässä ryhmässä."; /* No comment provided by engineer. */ "Voice messages prohibited!" = "Ääniviestit kielletty!"; @@ -3298,9 +3189,6 @@ /* No comment provided by engineer. */ "When available" = "Kun saatavilla"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Kun ihmiset pyytävät yhteyden muodostamista, voit hyväksyä tai hylätä sen."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Kun jaat inkognitoprofiilin jonkun kanssa, tätä profiilia käytetään ryhmissä, joihin tämä sinut kutsuu."; @@ -3316,9 +3204,6 @@ /* pref value */ "yes" = "kyllä"; -/* No comment provided by engineer. */ -"You" = "Sinä"; - /* No comment provided by engineer. */ "You accepted connection" = "Hyväksyit yhteyden"; @@ -3370,9 +3255,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Voit jakaa tämän osoitteen kontaktiesi kanssa, jotta ne voivat muodostaa yhteyden **%@** kanssa."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Voit jakaa osoitteesi linkkinä tai QR-koodina - kuka tahansa voi muodostaa yhteyden sinuun."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Voit aloittaa keskustelun sovelluksen Asetukset / Tietokanta kautta tai käynnistämällä sovelluksen uudelleen"; @@ -3398,10 +3280,10 @@ "you changed role of %@ to %@" = "olet vaihtanut %1$@:n roolin %2$@:ksi"; /* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Sinä hallitset, minkä palvelim(i)en kautta **viestit vastaanotetaan**, kontaktisi - palvelimet, joita käytät viestien lähettämiseen niille."; +"You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen."; /* No comment provided by engineer. */ -"You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen."; +"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."; @@ -3469,9 +3351,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Käytät tässä ryhmässä incognito-profiilia. Kontaktien kutsuminen ei ole sallittua, jotta pääprofiilisi ei tule jaetuksi"; -/* No comment provided by engineer. */ -"Your %@ servers" = "%@-palvelimesi"; - /* No comment provided by engineer. */ "Your calls" = "Puhelusi"; @@ -3512,7 +3391,7 @@ "Your profile **%@** will be shared." = "Profiilisi **%@** jaetaan."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa.\nSimpleX-palvelimet eivät näe profiiliasi."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi."; @@ -3520,9 +3399,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Satunnainen profiilisi"; -/* No comment provided by engineer. */ -"Your server" = "Palvelimesi"; - /* No comment provided by engineer. */ "Your server address" = "Palvelimesi osoite"; @@ -3532,9 +3408,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "SimpleX-osoitteesi"; -/* No comment provided by engineer. */ -"Your SMP servers" = "SMP-palvelimesi"; - -/* No comment provided by engineer. */ -"Your XFTP servers" = "XFTP-palvelimesi"; - diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index b4f256762c..4dd75039dc 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (peut être copié)"; @@ -31,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- messages vocaux pouvant durer jusqu'à 5 minutes.\n- délai personnalisé de disparition.\n- l'historique de modification."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 coloré!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nouveau)"; /* No comment provided by engineer. */ "(this device v%@)" = "(cet appareil v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuer](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -65,10 +35,7 @@ "[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. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation ou vous connecter via un lien que vous avez reçu."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Ajouter un nouveau contact** : pour créer un lien ou code QR unique pour votre contact."; +"**Create 1-time link**: to create and share a new invitation link." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Créer un groupe** : pour créer un nouveau groupe."; @@ -80,10 +47,10 @@ "**e2e encrypted** video call" = "appel vidéo **chiffré de bout en bout**"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Vie privée** : vérification de nouveaux messages toute les 20 minutes. Le token de l'appareil est partagé avec le serveur SimpleX, mais pas le nombre de messages ou de contacts."; +"**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." = "**Vie privée** : vérification de nouveaux messages toute les 20 minutes. Le token de l'appareil est partagé avec le serveur SimpleX, mais pas le nombre de messages ou de contacts."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app)."; +"**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." = "**Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app)."; /* 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." = "**Remarque** : l'utilisation de la même base de données sur deux appareils interrompt le déchiffrement des messages provenant de vos connexions, par mesure de sécurité."; @@ -92,7 +59,10 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur."; +"**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." = "**Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur."; + +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Scanner / Coller** : pour vous connecter via un lien que vous avez reçu."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain."; @@ -154,12 +124,21 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ est vérifié·e"; +/* No comment provided by engineer. */ +"%@ server" = "Serveur %@"; + +/* No comment provided by engineer. */ +"%@ servers" = "Serveurs %@"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ envoyé"; /* notification title */ "%@ wants to connect!" = "%@ veut se connecter !"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ et %lld membres"; @@ -172,9 +151,24 @@ /* time interval */ "%d days" = "%d jours"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d fichier(s) en cours de téléchargement."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "Le téléchargement de %d fichier(s) a échoué."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "Le(s) fichier(s) %d a(ont) été supprimé(s)."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "Le(s) fichier(s) %d n'a (n'ont) pas été téléchargé(s)."; + /* time interval */ "%d hours" = "%d heures"; +/* alert title */ +"%d messages not forwarded" = "%d messages non transférés"; + /* time interval */ "%d min" = "%d min"; @@ -184,6 +178,9 @@ /* time interval */ "%d sec" = "%d sec"; +/* delete after time */ +"%d seconds(s)" = "%d seconde(s)"; + /* integrity error chat item */ "%d skipped message(s)" = "%d message·s sauté·s"; @@ -226,9 +223,6 @@ /* No comment provided by engineer. */ "%lld new interface languages" = "%lld nouvelles langues d'interface"; -/* No comment provided by engineer. */ -"%lld second(s)" = "%lld seconde·s"; - /* No comment provided by engineer. */ "%lld seconds" = "%lld secondes"; @@ -274,7 +268,8 @@ /* No comment provided by engineer. */ "0s" = "0s"; -/* time interval */ +/* delete after time +time interval */ "1 day" = "1 jour"; /* time interval */ @@ -283,12 +278,23 @@ /* No comment provided by engineer. */ "1 minute" = "1 minute"; -/* time interval */ +/* delete after time +time interval */ "1 month" = "1 mois"; -/* time interval */ +/* delete after time +time interval */ "1 week" = "1 semaine"; +/* delete after time */ +"1 year" = "1 an"; + +/* No comment provided by engineer. */ +"1-time link" = "Lien unique"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Le lien unique peut être utilisé *avec un seul contact* - partagez le en personne ou via n'importe quelle messagerie."; + /* No comment provided by engineer. */ "5 minutes" = "5 minutes"; @@ -323,10 +329,7 @@ "Abort changing address?" = "Abandonner le changement d'adresse ?"; /* No comment provided by engineer. */ -"About SimpleX" = "À propos de SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "À propos de l'adresse SimpleX"; +"About operators" = "À propos des opérateurs"; /* No comment provided by engineer. */ "About SimpleX Chat" = "À propos de SimpleX Chat"; @@ -338,10 +341,13 @@ "Accent" = "Principale"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Accepter"; +/* No comment provided by engineer. */ +"Accept conditions" = "Accepter les conditions"; + /* No comment provided by engineer. */ "Accept connection request?" = "Accepter la demande de connexion ?"; @@ -349,18 +355,27 @@ "Accept contact request from %@?" = "Accepter la demande de contact de %@ ?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Accepter en incognito"; /* call status */ "accepted call" = "appel accepté"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Conditions acceptées"; + +/* chat list item title */ +"accepted invitation" = "invitation acceptée"; + /* No comment provided by engineer. */ "Acknowledged" = "Reçu avec accusé de réception"; /* No comment provided by engineer. */ "Acknowledgement errors" = "Erreur d'accusé de réception"; +/* token status text */ +"Active" = "Actif"; + /* No comment provided by engineer. */ "Active connections" = "Connections actives"; @@ -368,10 +383,10 @@ "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 contact" = "Ajouter le contact"; +"Add friends" = "Ajouter des amis"; /* No comment provided by engineer. */ -"Add preset servers" = "Ajouter des serveurs prédéfinis"; +"Add list" = "Ajouter une liste"; /* No comment provided by engineer. */ "Add profile" = "Ajouter un profil"; @@ -382,12 +397,27 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Ajoutez des serveurs en scannant des codes QR."; +/* No comment provided by engineer. */ +"Add team members" = "Ajouter des membres à l'équipe"; + /* No comment provided by engineer. */ "Add to another device" = "Ajouter à un autre appareil"; +/* No comment provided by engineer. */ +"Add to list" = "Ajouter à la liste"; + /* No comment provided by engineer. */ "Add welcome message" = "Ajouter un message d'accueil"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Ajoutez les membres de votre équipe aux conversations."; + +/* No comment provided by engineer. */ +"Added media & file servers" = "Ajout de serveurs de médias et de fichiers"; + +/* No comment provided by engineer. */ +"Added message servers" = "Ajout de serveurs de messages"; + /* No comment provided by engineer. */ "Additional accent" = "Accent additionnel"; @@ -403,6 +433,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Le changement d'adresse sera annulé. L'ancienne adresse de réception sera utilisée."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Adresse ou lien unique ?"; + +/* No comment provided by engineer. */ +"Address settings" = "Paramètres de l'adresse"; + /* member role */ "admin" = "admin"; @@ -427,17 +463,23 @@ /* chat item text */ "agreeing encryption…" = "négociation du chiffrement…"; +/* No comment provided by engineer. */ +"All" = "Tout"; + /* No comment provided by engineer. */ "All app data is deleted." = "Toutes les données de l'application sont supprimées."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Toutes les discussions et tous les messages seront supprimés - il est impossible de revenir en arrière !"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Tous les chats seront supprimés de la liste %@, et la liste sera supprimée."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Toutes les données sont effacées lorsqu'il est saisi."; /* No comment provided by engineer. */ -"All data is private to your device." = "Toutes les données restent confinées dans votre appareil."; +"All data is kept private on your device." = "Toutes les données restent confinées dans votre appareil."; /* No comment provided by engineer. */ "All group members will remain connected." = "Tous les membres du groupe resteront connectés."; @@ -445,6 +487,9 @@ /* feature role */ "all members" = "tous les membres"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Tous les messages et fichiers sont envoyés **chiffrés de bout en bout**, avec une sécurité post-quantique dans les messages directs."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Tous les messages seront supprimés - il n'est pas possible de revenir en arrière !"; @@ -454,9 +499,12 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Tous les nouveaux messages de %@ seront cachés !"; -/* No comment provided by engineer. */ +/* profile dropdown */ "All profiles" = "Tous les profiles"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Tous les rapports seront archivés pour vous."; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Tous vos contacts resteront connectés."; @@ -502,6 +550,9 @@ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Autoriser la suppression irréversible de messages envoyés. (24 heures)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Permettre de signaler des messages aux modérateurs."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Permet l'envoi de fichiers et de médias."; @@ -556,9 +607,15 @@ /* No comment provided by engineer. */ "and %lld other events" = "et %lld autres événements"; +/* report reason */ +"Another reason" = "Autre raison"; + /* 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 : %@"; @@ -577,6 +634,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Le code d'accès de l'application est remplacé par un code d'autodestruction."; +/* No comment provided by engineer. */ +"App session" = "Session de l'app"; + /* No comment provided by engineer. */ "App version" = "Version de l'app"; @@ -592,12 +652,30 @@ /* No comment provided by engineer. */ "Apply to" = "Appliquer à"; +/* No comment provided by engineer. */ +"Archive" = "Archiver"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "Archiver les rapports %lld ?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Archiver tous les rapports ?"; + /* No comment provided by engineer. */ "Archive and upload" = "Archiver et téléverser"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Archiver les contacts pour discuter plus tard."; +/* No comment provided by engineer. */ +"Archive report" = "Archiver le rapport"; + +/* No comment provided by engineer. */ +"Archive report?" = "Archiver le rapport ?"; + +/* swipe action */ +"Archive reports" = "Archiver les rapports"; + /* No comment provided by engineer. */ "Archived contacts" = "Contacts archivés"; @@ -649,6 +727,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Images auto-acceptées"; +/* alert title */ +"Auto-accept settings" = "Paramètres de réception automatique"; + /* No comment provided by engineer. */ "Back" = "Retour"; @@ -670,15 +751,36 @@ /* No comment provided by engineer. */ "Bad message ID" = "Mauvais ID de message"; +/* No comment provided by engineer. */ +"Better calls" = "Appels améliorés"; + /* No comment provided by engineer. */ "Better groups" = "Des groupes plus performants"; +/* No comment provided by engineer. */ +"Better groups performance" = "Meilleure performance des groupes"; + +/* No comment provided by engineer. */ +"Better message dates." = "Meilleures dates de messages."; + /* No comment provided by engineer. */ "Better messages" = "Meilleurs messages"; /* No comment provided by engineer. */ "Better networking" = "Meilleure gestion de réseau"; +/* No comment provided by engineer. */ +"Better notifications" = "Notifications améliorées"; + +/* No comment provided by engineer. */ +"Better privacy and security" = "Meilleure protection de la privacité et de la sécurité"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Sécurité accrue ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Une meilleure expérience pour l'utilisateur"; + /* No comment provided by engineer. */ "Black" = "Noir"; @@ -706,7 +808,8 @@ /* rcv group event chat item */ "blocked %@" = "%@ bloqué"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "bloqué par l'administrateur"; /* No comment provided by engineer. */ @@ -739,9 +842,21 @@ /* 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. */ +"Business address" = "Adresse professionnelle"; + +/* No comment provided by engineer. */ +"Business chats" = "Discussions professionnelles"; + +/* No comment provided by engineer. */ +"Businesses" = "Entreprises"; + /* 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"; @@ -781,7 +896,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossible d'envoyer un message à ce membre"; -/* No comment provided by engineer. */ +/* alert action +alert button */ "Cancel" = "Annuler"; /* No comment provided by engineer. */ @@ -796,7 +912,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Impossible de transférer le message"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Impossible de recevoir le fichier"; /* snd error text */ @@ -808,6 +924,12 @@ /* No comment provided by engineer. */ "Change" = "Changer"; +/* alert title */ +"Change automatic message deletion?" = "Modifier la suppression automatique des messages ?"; + +/* authentication reason */ +"Change chat profiles" = "Changer de profil de discussion"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Changer la phrase secrète de la base de données ?"; @@ -833,7 +955,7 @@ "Change self-destruct mode" = "Modifier le mode d'autodestruction"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Modifier le code d'autodestruction"; /* chat item text */ @@ -852,7 +974,13 @@ "changing address…" = "changement d'adresse…"; /* No comment provided by engineer. */ -"Chat archive" = "Archives du chat"; +"Chat" = "Discussions"; + +/* No comment provided by engineer. */ +"Chat already exists" = "La discussion existe déjà"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "La discussion existe déjà !"; /* No comment provided by engineer. */ "Chat colors" = "Couleurs de chat"; @@ -890,13 +1018,31 @@ /* No comment provided by engineer. */ "Chat preferences" = "Préférences de chat"; +/* alert message */ +"Chat preferences were changed." = "Les préférences de discussion ont été modifiées."; + +/* No comment provided by engineer. */ +"Chat profile" = "Profil d'utilisateur"; + /* No comment provided by engineer. */ "Chat theme" = "Thème de chat"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "La discussion sera supprimé pour tous les membres - cela ne peut pas être annulé !"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "Le discussion sera supprimé pour vous - il n'est pas possible de revenir en arrière !"; + /* No comment provided by engineer. */ "Chats" = "Discussions"; /* No comment provided by engineer. */ +"Check messages every 20 min." = "Consulter les messages toutes les 20 minutes."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Consulter les messages quand c'est possible."; + +/* alert title */ "Check server address and try again." = "Vérifiez l'adresse du serveur et réessayez."; /* No comment provided by engineer. */ @@ -929,6 +1075,12 @@ /* No comment provided by engineer. */ "Clear conversation?" = "Effacer la conversation ?"; +/* No comment provided by engineer. */ +"Clear group?" = "Vider le groupe ?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "Vider ou supprimer le groupe ?"; + /* No comment provided by engineer. */ "Clear private notes?" = "Effacer les notes privées ?"; @@ -944,6 +1096,9 @@ /* No comment provided by engineer. */ "colored" = "coloré"; +/* report reason */ +"Community guidelines violation" = "Infraction aux règles communautaires"; + /* server test step */ "Compare file" = "Comparer le fichier"; @@ -956,11 +1111,32 @@ /* No comment provided by engineer. */ "Completed" = "Complétées"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Conditions acceptées le : %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Les conditions sont acceptées pour le(s) opérateur(s) : **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for these operator(s): **%@**." = "Les conditions sont déjà acceptées pour ces opérateurs : **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Conditions d'utilisation"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Les conditions seront acceptées pour le(s) opérateur(s) : **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Les conditions seront acceptées le : %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Les conditions seront automatiquement acceptées pour les opérateurs activés le : %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Configurer les serveurs ICE"; /* No comment provided by engineer. */ -"Configured %@ servers" = "%@ serveurs configurés"; +"Configure server operators" = "Configurer les opérateurs de serveur"; /* No comment provided by engineer. */ "Confirm" = "Confirmer"; @@ -992,6 +1168,9 @@ /* No comment provided by engineer. */ "Confirm upload" = "Confirmer la transmission"; +/* token status text */ +"Confirmed" = "Confirmé"; + /* server test step */ "Connect" = "Se connecter"; @@ -1082,7 +1261,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Connexion au bureau"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "connexion…"; /* No comment provided by engineer. */ @@ -1091,6 +1270,9 @@ /* No comment provided by engineer. */ "Connection and servers status." = "État de la connexion et des serveurs."; +/* No comment provided by engineer. */ +"Connection blocked" = "Connexion bloquée"; + /* No comment provided by engineer. */ "Connection error" = "Erreur de connexion"; @@ -1100,12 +1282,24 @@ /* chat list item title (it should not be shown */ "connection established" = "connexion établie"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "La connexion est bloquée par l'opérateur du serveur :\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "La connexion n'est pas prête."; + /* No comment provided by engineer. */ "Connection notifications" = "Notifications de connexion"; /* No comment provided by engineer. */ "Connection request sent!" = "Demande de connexion envoyée !"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "La connexion nécessite une renégociation du cryptage."; + +/* No comment provided by engineer. */ +"Connection security" = "Sécurité des connexions"; + /* No comment provided by engineer. */ "Connection terminated" = "Connexion terminée"; @@ -1163,6 +1357,9 @@ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "Vos contacts peuvent marquer les messages pour les supprimer ; vous pourrez les consulter."; +/* blocking reason */ +"Content violates conditions of use" = "Le contenu enfreint les conditions d'utilisation"; + /* No comment provided by engineer. */ "Continue" = "Continuer"; @@ -1178,6 +1375,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Version du cœur : v%@"; +/* No comment provided by engineer. */ +"Corner" = "Coin"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Corriger le nom pour %@ ?"; @@ -1185,10 +1385,10 @@ "Create" = "Créer"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "Création de groupes via un profil aléatoire."; +"Create 1-time link" = "Créer un lien unique"; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter."; +"Create a group using a random profile." = "Création de groupes via un profil aléatoire."; /* server test step */ "Create file" = "Créer un fichier"; @@ -1202,6 +1402,9 @@ /* No comment provided by engineer. */ "Create link" = "Créer un lien"; +/* No comment provided by engineer. */ +"Create list" = "Créer une liste"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Créer un nouveau profil sur [l'application de bureau](https://simplex.chat/downloads/). 💻"; @@ -1229,9 +1432,6 @@ /* copied message info */ "Created at: %@" = "Créé à : %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Créé le %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Création d'un lien d'archive"; @@ -1241,6 +1441,9 @@ /* No comment provided by engineer. */ "creator" = "créateur"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Le texte sur les conditions actuelles n'a pas pu être chargé. Vous pouvez consulter les conditions en cliquant sur ce lien :"; + /* No comment provided by engineer. */ "Current Passcode" = "Code d'accès actuel"; @@ -1259,6 +1462,9 @@ /* No comment provided by engineer. */ "Custom time" = "Délai personnalisé"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Forme des messages personnalisable."; + /* No comment provided by engineer. */ "Customize theme" = "Personnaliser le thème"; @@ -1340,7 +1546,8 @@ /* No comment provided by engineer. */ "decryption errors" = "Erreurs de déchiffrement"; -/* pref value */ +/* delete after time +pref value */ "default (%@)" = "défaut (%@)"; /* No comment provided by engineer. */ @@ -1349,8 +1556,8 @@ /* No comment provided by engineer. */ "default (yes)" = "par défaut (oui)"; -/* chat item action - swipe action */ +/* alert action +swipe action */ "Delete" = "Supprimer"; /* No comment provided by engineer. */ @@ -1375,10 +1582,10 @@ "Delete and notify contact" = "Supprimer et en informer le contact"; /* No comment provided by engineer. */ -"Delete archive" = "Supprimer l'archive"; +"Delete chat" = "Supprimer la discussion"; /* No comment provided by engineer. */ -"Delete chat archive?" = "Supprimer l'archive du chat ?"; +"Delete chat messages from your device." = "Supprimer les messages de chat de votre appareil."; /* No comment provided by engineer. */ "Delete chat profile" = "Supprimer le profil de chat"; @@ -1386,6 +1593,9 @@ /* No comment provided by engineer. */ "Delete chat profile?" = "Supprimer le profil du chat ?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Supprimer la discussion ?"; + /* No comment provided by engineer. */ "Delete connection" = "Supprimer la connexion"; @@ -1431,13 +1641,16 @@ /* No comment provided by engineer. */ "Delete link?" = "Supprimer le lien ?"; +/* alert title */ +"Delete list?" = "Supprimer la liste ?"; + /* No comment provided by engineer. */ "Delete member message?" = "Supprimer le message de ce membre ?"; /* No comment provided by engineer. */ "Delete message?" = "Supprimer le message ?"; -/* No comment provided by engineer. */ +/* alert button */ "Delete messages" = "Supprimer les messages"; /* No comment provided by engineer. */ @@ -1449,6 +1662,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Supprimer l'ancienne base de données ?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Supprimer ou modérer jusqu'à 200 messages."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Supprimer la connexion en attente ?"; @@ -1458,6 +1674,9 @@ /* server test step */ "Delete queue" = "Supprimer la file d'attente"; +/* No comment provided by engineer. */ +"Delete report" = "Supprimer le rapport"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Supprimez jusqu'à 20 messages à la fois."; @@ -1488,6 +1707,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Erreurs de suppression"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Distribués même quand Apple les oublie."; + /* No comment provided by engineer. */ "Delivery" = "Distribution"; @@ -1555,11 +1777,20 @@ "Direct messages" = "Messages directs"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Les messages directs entre membres sont interdits dans ce groupe."; +"Direct messages between members are prohibited in this chat." = "Les messages directs entre membres sont interdits dans cette discussion."; + +/* No comment provided by engineer. */ +"Direct messages between members are prohibited." = "Les messages directs entre membres sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Désactiver (conserver les remplacements)"; +/* alert title */ +"Disable automatic message deletion?" = "Désactiver la suppression automatique des messages ?"; + +/* alert button */ +"Disable delete messages" = "Désactiver la suppression des messages"; + /* No comment provided by engineer. */ "Disable for all" = "Désactiver pour tous"; @@ -1582,7 +1813,7 @@ "Disappearing messages are prohibited in this chat." = "Les messages éphémères sont interdits dans cette discussion."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Les messages éphémères sont interdits dans ce groupe."; +"Disappearing messages are prohibited." = "Les messages éphémères sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Disappears at" = "Disparaîtra le"; @@ -1611,25 +1842,38 @@ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Ne pas envoyer de messages directement, même si votre serveur ou le serveur de destination ne prend pas en charge le routage privé."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Ne pas utiliser d'identifiants avec le proxy."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "Ne pas utiliser de routage privé."; /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "N'utilisez PAS SimpleX pour les appels d'urgence."; +/* No comment provided by engineer. */ +"Documents:" = "Documents:"; + /* No comment provided by engineer. */ "Don't create address" = "Ne pas créer d'adresse"; /* No comment provided by engineer. */ "Don't enable" = "Ne pas activer"; +/* No comment provided by engineer. */ +"Don't miss important messages." = "Ne manquez pas les messages importants."; + /* No comment provided by engineer. */ "Don't show again" = "Ne plus afficher"; +/* No comment provided by engineer. */ +"Done" = "Terminé"; + /* No comment provided by engineer. */ "Downgrade and open chat" = "Rétrograder et ouvrir le chat"; -/* chat item action */ +/* alert button +chat item action */ "Download" = "Télécharger"; /* No comment provided by engineer. */ @@ -1641,6 +1885,9 @@ /* server test step */ "Download file" = "Télécharger le fichier"; +/* alert action */ +"Download files" = "Télécharger les fichiers"; + /* No comment provided by engineer. */ "Downloaded" = "Téléchargé"; @@ -1668,6 +1915,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "chiffré de bout en bout"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "Notifications chiffrées E2E."; + /* chat item action */ "Edit" = "Modifier"; @@ -1680,12 +1930,15 @@ /* No comment provided by engineer. */ "Enable (keep overrides)" = "Activer (conserver les remplacements)"; -/* No comment provided by engineer. */ +/* alert title */ "Enable automatic message deletion?" = "Activer la suppression automatique des messages ?"; /* No comment provided by engineer. */ "Enable camera access" = "Autoriser l'accès à la caméra"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Activez Flux dans les paramètres du réseau et des serveurs pour une meilleure confidentialité des métadonnées."; + /* No comment provided by engineer. */ "Enable for all" = "Activer pour tous"; @@ -1797,6 +2050,9 @@ /* chat item text */ "encryption re-negotiation required for %@" = "renégociation de chiffrement requise pour %@"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "Renégociation du chiffrement en cours."; + /* No comment provided by engineer. */ "ended" = "terminé"; @@ -1845,24 +2101,36 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Erreur lors de l'annulation du changement d'adresse"; +/* alert title */ +"Error accepting conditions" = "Erreur lors de la validation des conditions"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Erreur de validation de la demande de contact"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Erreur d'accès au fichier de la base de données"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Erreur lors de l'ajout de membre·s"; +/* alert title */ +"Error adding server" = "Erreur lors de l'ajout du serveur"; + /* No comment provided by engineer. */ "Error changing address" = "Erreur de changement d'adresse"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Erreur lors du changement de profil de connexion"; + /* No comment provided by engineer. */ "Error changing role" = "Erreur lors du changement de rôle"; /* No comment provided by engineer. */ "Error changing setting" = "Erreur de changement de paramètre"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Erreur lors du passage en mode incognito !"; + +/* No comment provided by engineer. */ +"Error checking token status" = "Erreur lors de la vérification de l'état du jeton (token)"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard."; @@ -1875,6 +2143,9 @@ /* No comment provided by engineer. */ "Error creating group link" = "Erreur lors de la création du lien du groupe"; +/* alert title */ +"Error creating list" = "Erreur lors de la création de la liste"; + /* No comment provided by engineer. */ "Error creating member contact" = "Erreur lors de la création du contact du membre"; @@ -1884,6 +2155,9 @@ /* No comment provided by engineer. */ "Error creating profile!" = "Erreur lors de la création du profil !"; +/* No comment provided by engineer. */ +"Error creating report" = "Erreur lors de la création du rapport"; + /* No comment provided by engineer. */ "Error decrypting file" = "Erreur lors du déchiffrement du fichier"; @@ -1932,13 +2206,16 @@ /* No comment provided by engineer. */ "Error joining group" = "Erreur lors de la liaison avec le groupe"; +/* alert title */ +"Error loading servers" = "Erreur de chargement des serveurs"; + /* No comment provided by engineer. */ -"Error loading %@ servers" = "Erreur lors du chargement des serveurs %@"; +"Error migrating settings" = "Erreur lors de la migration des paramètres"; /* No comment provided by engineer. */ "Error opening chat" = "Erreur lors de l'ouverture du chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Erreur lors de la réception du fichier"; /* No comment provided by engineer. */ @@ -1947,14 +2224,20 @@ /* No comment provided by engineer. */ "Error reconnecting servers" = "Erreur de reconnexion des serveurs"; +/* alert title */ +"Error registering for notifications" = "Erreur lors de l'inscription aux notifications"; + /* No comment provided by engineer. */ "Error removing member" = "Erreur lors de la suppression d'un membre"; +/* alert title */ +"Error reordering lists" = "Erreur lors de la réorganisation des listes"; + /* No comment provided by engineer. */ "Error resetting statistics" = "Erreur de réinitialisation des statistiques"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Erreur lors de la sauvegarde des serveurs %@"; +/* alert title */ +"Error saving chat list" = "Erreur lors de l'enregistrement de la liste des chats"; /* No comment provided by engineer. */ "Error saving group profile" = "Erreur lors de la sauvegarde du profil de groupe"; @@ -1968,6 +2251,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Erreur lors de l'enregistrement de la phrase de passe dans la keychain"; +/* alert title */ +"Error saving servers" = "Erreur d'enregistrement des serveurs"; + /* when migrating */ "Error saving settings" = "Erreur lors de l'enregistrement des paramètres"; @@ -1996,17 +2282,26 @@ "Error stopping chat" = "Erreur lors de l'arrêt du chat"; /* No comment provided by engineer. */ +"Error switching profile" = "Erreur lors du changement de profil"; + +/* alertTitle */ "Error switching profile!" = "Erreur lors du changement de profil !"; /* No comment provided by engineer. */ "Error synchronizing connection" = "Erreur de synchronisation de connexion"; +/* No comment provided by engineer. */ +"Error testing server connection" = "Erreur lors du test de connexion au serveur"; + /* No comment provided by engineer. */ "Error updating group link" = "Erreur lors de la mise à jour du lien de groupe"; /* No comment provided by engineer. */ "Error updating message" = "Erreur lors de la mise à jour du message"; +/* alert title */ +"Error updating server" = "Erreur de mise à jour du serveur"; + /* No comment provided by engineer. */ "Error updating settings" = "Erreur lors de la mise à jour des paramètres"; @@ -2022,8 +2317,9 @@ /* No comment provided by engineer. */ "Error: " = "Erreur : "; -/* file error text - snd error text */ +/* alert message +file error text +snd error text */ "Error: %@" = "Erreur : %@"; /* No comment provided by engineer. */ @@ -2035,11 +2331,11 @@ /* No comment provided by engineer. */ "Errors" = "Erreurs"; -/* No comment provided by engineer. */ -"Even when disabled in the conversation." = "Même s'il est désactivé dans la conversation."; +/* servers error */ +"Errors in servers configuration." = "Erreurs dans la configuration des serveurs."; /* No comment provided by engineer. */ -"event happened" = "event happened"; +"Even when disabled in the conversation." = "Même s'il est désactivé dans la conversation."; /* No comment provided by engineer. */ "Exit without saving" = "Quitter sans enregistrer"; @@ -2050,6 +2346,9 @@ /* No comment provided by engineer. */ "expired" = "expiré"; +/* token status text */ +"Expired" = "Expiré"; + /* No comment provided by engineer. */ "Export database" = "Exporter la base de données"; @@ -2074,15 +2373,30 @@ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "Rapide et ne nécessitant pas d'attendre que l'expéditeur soit en ligne !"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "Suppression plus rapide des groupes."; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "Connexion plus rapide et messages plus fiables."; +/* No comment provided by engineer. */ +"Faster sending messages." = "Envoi plus rapide des messages."; + /* swipe action */ "Favorite" = "Favoris"; /* No comment provided by engineer. */ +"Favorites" = "Favoris"; + +/* file error alert title */ "File error" = "Erreur de fichier"; +/* alert message */ +"File errors:\n%@" = "Erreurs de fichier :\n%@"; + +/* file error text */ +"File is blocked by server operator:\n%@." = "Le fichier est bloqué par l'opérateur du serveur :\n%@."; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Fichier introuvable - le fichier a probablement été supprimé ou annulé."; @@ -2117,7 +2431,7 @@ "Files and media" = "Fichiers et médias"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Les fichiers et les médias sont interdits dans ce groupe."; +"Files and media are prohibited." = "Les fichiers et les médias sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Files and media not allowed" = "Fichiers et médias non autorisés"; @@ -2158,15 +2472,39 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Correction non prise en charge par un membre du groupe"; +/* servers error */ +"For chat profile %@:" = "Pour le profil de discussion %@ :"; + /* No comment provided by engineer. */ "For console" = "Pour la console"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Par exemple, si votre contact reçoit des messages via un serveur SimpleX Chat, votre application les transmettra via un serveur Flux."; + +/* No comment provided by engineer. */ +"For private routing" = "Pour le routage privé"; + +/* No comment provided by engineer. */ +"For social media" = "Pour les réseaux sociaux"; + /* chat item action */ "Forward" = "Transférer"; +/* alert title */ +"Forward %d message(s)?" = "Transférer %d message(s) ?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Transférer et sauvegarder des messages"; +/* alert action */ +"Forward messages" = "Transférer les messages"; + +/* alert message */ +"Forward messages without files?" = "Transférer les messages sans les fichiers ?"; + +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Transférez jusqu'à 20 messages à la fois."; + /* No comment provided by engineer. */ "forwarded" = "transféré"; @@ -2176,6 +2514,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Transféré depuis"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "Transfert des %lld messages"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard."; @@ -2203,9 +2544,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nom complet (optionnel)"; -/* No comment provided by engineer. */ -"Full name:" = "Nom complet :"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Entièrement décentralisé – visible que par ses membres."; @@ -2260,27 +2598,6 @@ /* No comment provided by engineer. */ "Group links" = "Liens de groupe"; -/* No comment provided by engineer. */ -"Group members can add message reactions." = "Les membres du groupe peuvent ajouter des réactions aux messages."; - -/* No comment provided by engineer. */ -"Group 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)"; - -/* No comment provided by engineer. */ -"Group members can send direct messages." = "Les membres du groupe peuvent envoyer des messages directs."; - -/* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères."; - -/* No comment provided by engineer. */ -"Group members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; - -/* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX."; - -/* No comment provided by engineer. */ -"Group members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; - /* notification */ "Group message:" = "Message du groupe :"; @@ -2342,7 +2659,10 @@ "hours" = "heures"; /* No comment provided by engineer. */ -"How it works" = "Comment ça fonctionne"; +"How it affects privacy" = "L'impact sur la vie privée"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Comment il contribue à la protection de la vie privée"; /* No comment provided by engineer. */ "How SimpleX works" = "Comment SimpleX fonctionne"; @@ -2387,7 +2707,7 @@ "Immediately" = "Immédiatement"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Protégé du spam et des abus"; +"Immune to spam" = "Protégé du spam et des abus"; /* No comment provided by engineer. */ "Import" = "Importer"; @@ -2407,6 +2727,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Importation de l'archive"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Amélioration de la distribution, réduction de l'utilisation du trafic.\nD'autres améliorations sont à venir !"; + /* No comment provided by engineer. */ "Improved message delivery" = "Amélioration de la transmission des messages"; @@ -2483,10 +2806,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Les notifications push instantanées vont être cachées !\n"; +"Instant" = "Instantané"; /* No comment provided by engineer. */ -"Instantly" = "Instantané"; +"Instant push notifications will be hidden!\n" = "Les notifications push instantanées vont être cachées !\n"; /* No comment provided by engineer. */ "Interface" = "Interface"; @@ -2524,7 +2847,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Réponse invalide"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Adresse de serveur invalide !"; /* item status text */ @@ -2545,6 +2868,9 @@ /* No comment provided by engineer. */ "Invite members" = "Inviter des membres"; +/* No comment provided by engineer. */ +"Invite to chat" = "Inviter à discuter"; + /* No comment provided by engineer. */ "Invite to group" = "Inviter au groupe"; @@ -2566,6 +2892,9 @@ /* 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." = "La keychain d'iOS sera utilisée pour stocker en toute sécurité la phrase secrète après le redémarrage de l'app ou la modification de la phrase secrète - il permettra de recevoir les notifications push."; +/* No comment provided by engineer. */ +"IP address" = "Adresse IP"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Suppression irréversible des messages"; @@ -2573,7 +2902,7 @@ "Irreversible message deletion is prohibited in this chat." = "La suppression irréversible de message est interdite dans ce chat."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "La suppression irréversible de messages est interdite dans ce groupe."; +"Irreversible message deletion is prohibited." = "La suppression irréversible de messages est interdite dans ce groupe."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Cela permet d'avoir plusieurs connections anonymes sans aucune données partagées entre elles sur un même profil."; @@ -2626,7 +2955,7 @@ /* No comment provided by engineer. */ "Joining group" = "Entrain de rejoindre le groupe"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Conserver"; /* No comment provided by engineer. */ @@ -2635,7 +2964,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Garder l'application ouverte pour l'utiliser depuis le bureau"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Conserver l'invitation inutilisée ?"; /* No comment provided by engineer. */ @@ -2656,6 +2985,12 @@ /* swipe action */ "Leave" = "Quitter"; +/* No comment provided by engineer. */ +"Leave chat" = "Quitter la discussion"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Quitter la discussion ?"; + /* No comment provided by engineer. */ "Leave group" = "Quitter le groupe"; @@ -2692,9 +3027,6 @@ /* No comment provided by engineer. */ "Live messages" = "Messages dynamiques"; -/* No comment provided by engineer. */ -"Local" = "Local"; - /* No comment provided by engineer. */ "Local name" = "Nom local"; @@ -2707,24 +3039,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Mode de verrouillage"; -/* No comment provided by engineer. */ -"Make a private connection" = "Établir une connexion privée"; - /* No comment provided by engineer. */ "Make one message disappear" = "Rendre un message éphémère"; /* No comment provided by engineer. */ "Make profile private!" = "Rendre un profil privé !"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Assurez-vous que les adresses des serveurs %@ sont au bon format et ne sont pas dupliquées, un par ligne (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Beaucoup se demandent : *si SimpleX n'a pas d'identifiant d'utilisateur, comment peut-il délivrer des messages ?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Marquer comme supprimé pour tout le monde"; @@ -2764,15 +3087,42 @@ /* item status text */ "Member inactive" = "Membre inactif"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "Le rôle du membre sera modifié pour « %@ ». Tous les membres du chat seront notifiés."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Le rôle du membre sera changé pour \"%@\". Tous les membres du groupe en seront informés."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Le rôle du membre sera changé pour \"%@\". Ce membre recevra une nouvelle invitation."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Le membre sera retiré de la discussion - cela ne peut pas être annulé !"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Ce membre sera retiré du groupe - impossible de revenir en arrière !"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Les membres du groupe peuvent ajouter des réactions aux messages."; + +/* 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)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Les membres du groupe peuvent envoyer des messages directs."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; + /* No comment provided by engineer. */ "Menus" = "Menus"; @@ -2807,7 +3157,7 @@ "Message reactions are prohibited in this chat." = "Les réactions aux messages sont interdites dans ce chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Les réactions aux messages sont interdites dans ce groupe."; +"Message reactions are prohibited." = "Les réactions aux messages sont interdites dans ce groupe."; /* notification */ "message received" = "message reçu"; @@ -2818,6 +3168,9 @@ /* No comment provided by engineer. */ "Message servers" = "Serveurs de messages"; +/* No comment provided by engineer. */ +"Message shape" = "Forme du message"; + /* No comment provided by engineer. */ "Message source remains private." = "La source du message reste privée."; @@ -2848,6 +3201,9 @@ /* No comment provided by engineer. */ "Messages sent" = "Messages envoyés"; +/* alert message */ +"Messages were deleted after you selected them." = "Les messages ont été supprimés après avoir été sélectionnés."; + /* 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." = "Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction."; @@ -2888,7 +3244,7 @@ "Migration is completed" = "La migration est terminée"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrations : %@"; +"Migrations:" = "Migrations :"; /* time unit */ "minutes" = "minutes"; @@ -2920,16 +3276,16 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Connexion réseau plus fiable."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Notifications plus fiables"; + /* item status description */ "Most likely this connection is deleted." = "Connexion probablement supprimée."; /* No comment provided by engineer. */ "Multiple chat profiles" = "Différents profils de chat"; -/* No comment provided by engineer. */ -"mute" = "muet"; - -/* swipe action */ +/* notification label action */ "Mute" = "Muet"; /* No comment provided by engineer. */ @@ -2944,19 +3300,25 @@ /* No comment provided by engineer. */ "Network connection" = "Connexion au réseau"; +/* No comment provided by engineer. */ +"Network decentralization" = "Décentralisation du réseau"; + /* snd error text */ "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."; /* No comment provided by engineer. */ "Network management" = "Gestion du réseau"; +/* No comment provided by engineer. */ +"Network operator" = "Opérateur de réseau"; + /* No comment provided by engineer. */ "Network settings" = "Paramètres réseau"; /* No comment provided by engineer. */ "Network status" = "État du réseau"; -/* No comment provided by engineer. */ +/* delete after time */ "never" = "jamais"; /* No comment provided by engineer. */ @@ -2971,15 +3333,15 @@ /* notification */ "New contact:" = "Nouveau contact :"; -/* No comment provided by engineer. */ -"New database archive" = "Nouvelle archive de base de données"; - /* No comment provided by engineer. */ "New desktop app!" = "Nouvelle application de bureau !"; /* No comment provided by engineer. */ "New display name" = "Nouveau nom d'affichage"; +/* notification */ +"New events" = "Nouveaux événements"; + /* No comment provided by engineer. */ "New in %@" = "Nouveautés de la %@"; @@ -3001,6 +3363,15 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nouvelle phrase secrète…"; +/* No comment provided by engineer. */ +"New server" = "Nouveau serveur"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "De nouveaux identifiants SOCKS seront utilisés chaque fois que vous démarrerez l'application."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "De nouveaux identifiants SOCKS seront utilisées pour chaque serveur."; + /* pref value */ "no" = "non"; @@ -3040,30 +3411,66 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Pas d'info, essayez de recharger"; +/* servers error */ +"No media & file servers." = "Pas de serveurs de médias et de fichiers."; + +/* servers error */ +"No message servers." = "Pas de serveurs de messages."; + /* No comment provided by engineer. */ "No network connection" = "Pas de connexion au réseau"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Enregistrement des conversations non autorisé"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Enregistrement de la vidéo non autorisé"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Pas l'autorisation d'enregistrer un message vocal"; +/* No comment provided by engineer. */ +"No push server" = "No push server"; + /* No comment provided by engineer. */ "No received or sent files" = "Aucun fichier reçu ou envoyé"; +/* servers error */ +"No servers for private message routing." = "Pas de serveurs pour le routage privé des messages."; + +/* servers error */ +"No servers to receive files." = "Pas de serveurs pour recevoir des fichiers."; + +/* servers error */ +"No servers to receive messages." = "Pas de serveurs pour recevoir des messages."; + +/* servers error */ +"No servers to send files." = "Pas de serveurs pour envoyer des fichiers."; + /* 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 !"; /* No comment provided by engineer. */ "Nothing selected" = "Aucune sélection"; +/* alert title */ +"Nothing to forward!" = "Rien à transférer !"; + /* No comment provided by engineer. */ "Notifications" = "Notifications"; /* No comment provided by engineer. */ "Notifications are disabled!" = "Les notifications sont désactivées !"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Notifications sécurisées"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Désormais, les administrateurs peuvent :\n- supprimer les messages des membres.\n- désactiver des membres (rôle \"observateur\")"; @@ -3071,8 +3478,8 @@ "observer" = "observateur"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "off"; /* blur media */ @@ -3084,7 +3491,7 @@ /* feature offered item */ "offered %@: %@" = "propose %1$@ : %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3093,9 +3500,6 @@ /* No comment provided by engineer. */ "Old database" = "Ancienne base de données"; -/* No comment provided by engineer. */ -"Old database archive" = "Archives de l'ancienne base de données"; - /* group pref value */ "on" = "on"; @@ -3112,7 +3516,10 @@ "Onion hosts will not be used." = "Les hôtes .onion ne seront pas utilisés."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**."; +"Only chat owners can change preferences." = "Seuls les propriétaires peuvent modifier les préférences."; + +/* No comment provided by engineer. */ +"Only client devices store user profiles, contacts, groups, and messages." = "Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**."; /* No comment provided by engineer. */ "Only delete conversation" = "Ne supprimer que la conversation"; @@ -3156,36 +3563,42 @@ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Seul votre contact peut envoyer des messages vocaux."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Ouvrir"; +/* No comment provided by engineer. */ +"Open changes" = "Ouvrir les modifications"; + /* No comment provided by engineer. */ "Open chat" = "Ouvrir le chat"; /* authentication reason */ "Open chat console" = "Ouvrir la console du chat"; +/* No comment provided by engineer. */ +"Open conditions" = "Ouvrir les conditions"; + /* No comment provided by engineer. */ "Open group" = "Ouvrir le groupe"; /* authentication reason */ "Open migration to another device" = "Ouvrir le transfert vers un autre appareil"; -/* No comment provided by engineer. */ -"Open server settings" = "Ouvrir les paramètres du serveur"; - /* No comment provided by engineer. */ "Open Settings" = "Ouvrir les Paramètres"; -/* authentication reason */ -"Open user profiles" = "Ouvrir les profils d'utilisateurs"; - -/* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protocole et code open-source – n'importe qui peut heberger un serveur."; - /* No comment provided by engineer. */ "Opening app…" = "Ouverture de l'app…"; +/* No comment provided by engineer. */ +"Operator" = "Opérateur"; + +/* alert title */ +"Operator server" = "Serveur de l'opérateur"; + +/* No comment provided by engineer. */ +"Or import archive file" = "Ou importer un fichier d'archive"; + /* No comment provided by engineer. */ "Or paste archive link" = "Ou coller le lien de l'archive"; @@ -3196,7 +3609,10 @@ "Or securely share this file link" = "Ou partagez en toute sécurité le lien de ce fichier"; /* No comment provided by engineer. */ -"Or show this code" = "Ou présenter ce code"; +"Or show this code" = "Ou montrez ce code"; + +/* No comment provided by engineer. */ +"Or to share privately" = "Ou à partager en privé"; /* No comment provided by engineer. */ "other" = "autre"; @@ -3204,12 +3620,12 @@ /* No comment provided by engineer. */ "Other" = "Autres"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Autres serveurs %@"; - /* No comment provided by engineer. */ "other errors" = "autres erreurs"; +/* alert message */ +"Other file errors:\n%@" = "Autres erreurs de fichiers :\n%@"; + /* member role */ "owner" = "propriétaire"; @@ -3231,6 +3647,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Code d'accès défini !"; +/* No comment provided by engineer. */ +"Password" = "Mot de passe"; + /* No comment provided by engineer. */ "Password to show" = "Mot de passe à entrer"; @@ -3256,10 +3675,7 @@ "Pending" = "En attente"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "On ne peut se connecter à vous qu’avec les liens que vous partagez."; - -/* No comment provided by engineer. */ -"Periodically" = "Périodique"; +"Periodic" = "Périodique"; /* message decrypt error item */ "Permanent decryption error" = "Erreur de déchiffrement"; @@ -3327,6 +3743,9 @@ /* No comment provided by engineer. */ "Polish interface" = "Interface en polonais"; +/* No comment provided by engineer. */ +"Port" = "Port"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte"; @@ -3334,10 +3753,10 @@ "Preserve the last message draft, with attachments." = "Conserver le brouillon du dernier message, avec les pièces jointes."; /* No comment provided by engineer. */ -"Preset server" = "Serveur prédéfini"; +"Preset server address" = "Adresse du serveur prédéfinie"; /* No comment provided by engineer. */ -"Preset server address" = "Adresse du serveur prédéfinie"; +"Preset servers" = "Serveurs prédéfinis"; /* No comment provided by engineer. */ "Preview" = "Aperçu"; @@ -3348,6 +3767,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Vie privée et sécurité"; +/* 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"; @@ -3378,19 +3800,13 @@ /* No comment provided by engineer. */ "Profile images" = "Images de profil"; -/* No comment provided by engineer. */ -"Profile name" = "Nom du profil"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nom du profil :"; - /* No comment provided by engineer. */ "Profile password" = "Mot de passe de profil"; /* No comment provided by engineer. */ "Profile theme" = "Thème de profil"; -/* No comment provided by engineer. */ +/* 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. */ @@ -3444,6 +3860,9 @@ /* No comment provided by engineer. */ "Proxied servers" = "Serveurs routés via des proxy"; +/* No comment provided by engineer. */ +"Proxy requires password" = "Le proxy est protégé par un mot de passe"; + /* No comment provided by engineer. */ "Push notifications" = "Notifications push"; @@ -3472,10 +3891,10 @@ "Read more" = "En savoir plus"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "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](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)."; @@ -3483,9 +3902,6 @@ /* 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)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Plus d'informations sur notre GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Les accusés de réception sont désactivés"; @@ -3568,7 +3984,7 @@ "Reduced battery usage" = "Réduction de la consommation de batterie"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Rejeter"; /* No comment provided by engineer. */ @@ -3589,6 +4005,9 @@ /* No comment provided by engineer. */ "Remove" = "Supprimer"; +/* No comment provided by engineer. */ +"Remove archive?" = "Supprimer l'archive ?"; + /* No comment provided by engineer. */ "Remove image" = "Enlever l'image"; @@ -3643,6 +4062,9 @@ /* chat item action */ "Reply" = "Répondre"; +/* chat list item title */ +"requested to connect" = "demande à se connecter"; + /* No comment provided by engineer. */ "Required" = "Requis"; @@ -3694,6 +4116,9 @@ /* chat item action */ "Reveal" = "Révéler"; +/* No comment provided by engineer. */ +"Review conditions" = "Vérifier les conditions"; + /* No comment provided by engineer. */ "Revoke" = "Révoquer"; @@ -3715,13 +4140,14 @@ /* No comment provided by engineer. */ "Safer groups" = "Groupes plus sûrs"; -/* chat item action */ +/* alert button +chat item action */ "Save" = "Enregistrer"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Enregistrer (et en informer les contacts)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Enregistrer et en informer le contact"; /* No comment provided by engineer. */ @@ -3733,12 +4159,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Enregistrer et mettre à jour le profil du groupe"; -/* No comment provided by engineer. */ -"Save archive" = "Enregistrer l'archive"; - -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Enregistrer les paramètres de validation automatique"; - /* No comment provided by engineer. */ "Save group profile" = "Enregistrer le profil du groupe"; @@ -3748,7 +4168,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Enregistrer la phrase secrète dans la Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Enregistrer les préférences ?"; /* No comment provided by engineer. */ @@ -3757,15 +4177,15 @@ /* No comment provided by engineer. */ "Save servers" = "Enregistrer les serveurs"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Enregistrer les serveurs ?"; -/* No comment provided by engineer. */ -"Save settings?" = "Enregistrer les paramètres ?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Enregistrer le message d'accueil ?"; +/* alert title */ +"Save your profile?" = "Sauvegarder votre profil ?"; + /* No comment provided by engineer. */ "saved" = "enregistré"; @@ -3784,11 +4204,14 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Les serveurs WebRTC ICE sauvegardés seront supprimés"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Sauvegarde de %lld messages"; + /* No comment provided by engineer. */ "Scale" = "Échelle"; /* No comment provided by engineer. */ -"Scan / Paste link" = "Scanner / Coller le lien"; +"Scan / Paste link" = "Scanner / Coller un lien"; /* No comment provided by engineer. */ "Scan code" = "Scanner le code"; @@ -3847,6 +4270,9 @@ /* chat item action */ "Select" = "Choisir"; +/* No comment provided by engineer. */ +"Select chat profile" = "Sélectionner un profil de discussion"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld sélectionné(s)"; @@ -3904,9 +4330,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Envoi de notifications"; -/* No comment provided by engineer. */ -"Send notifications:" = "Envoi de notifications :"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Envoyez vos questions et idées"; @@ -3919,7 +4342,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Envoi des 100 derniers messages aux nouveaux membres."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "L'expéditeur a annulé le transfert de fichiers."; /* No comment provided by engineer. */ @@ -3979,6 +4402,12 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Envoyé via le proxy"; +/* No comment provided by engineer. */ +"Server" = "Serveur"; + +/* alert message */ +"Server added to operator %@." = "Serveur ajouté à l'opérateur %@."; + /* No comment provided by engineer. */ "Server address" = "Adresse du serveur"; @@ -3988,6 +4417,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "L'adresse du serveur est incompatible avec les paramètres du réseau."; +/* alert title */ +"Server operator changed." = "L'opérateur du serveur a changé."; + +/* No comment provided by engineer. */ +"Server operators" = "Opérateurs de serveur"; + +/* alert title */ +"Server protocol changed." = "Le protocole du serveur a été modifié."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "info sur la file d'attente du serveur : %1$@\n\ndernier message reçu : %2$@"; @@ -4060,19 +4498,29 @@ /* No comment provided by engineer. */ "Settings" = "Paramètres"; +/* alert message */ +"Settings were changed." = "Les paramètres ont été modifiés."; + /* No comment provided by engineer. */ "Shape profile images" = "Images de profil modelable"; -/* chat item action */ +/* alert action +chat item action */ "Share" = "Partager"; /* No comment provided by engineer. */ "Share 1-time link" = "Partager un lien unique"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Partager un lien unique avec un ami"; + /* No comment provided by engineer. */ "Share address" = "Partager l'adresse"; /* 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. */ @@ -4082,7 +4530,13 @@ "Share link" = "Partager le lien"; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Partager ce lien d'invitation unique"; +"Share profile" = "Partager le profil"; + +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Partagez votre adresse SimpleX sur les réseaux sociaux."; + +/* No comment provided by engineer. */ +"Share this 1-time invite link" = "Partagez ce lien d'invitation unique"; /* No comment provided by engineer. */ "Share to SimpleX" = "Partager sur SimpleX"; @@ -4126,6 +4580,15 @@ /* No comment provided by engineer. */ "SimpleX Address" = "Adresse SimpleX"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "Les adresses SimpleX et les liens à usage unique peuvent être partagés en toute sécurité via n'importe quelle messagerie."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "Adresse SimpleX ou lien unique ?"; + +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat et Flux ont conclu un accord pour inclure les serveurs exploités par Flux dans l'application."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "La sécurité de SimpleX Chat a été auditée par Trail of Bits."; @@ -4142,7 +4605,7 @@ "SimpleX links" = "Liens SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "Les liens SimpleX sont interdits dans ce groupe."; +"SimpleX links are prohibited." = "Les liens SimpleX sont interdits dans ce groupe."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Les liens SimpleX ne sont pas autorisés"; @@ -4162,6 +4625,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Invitation unique SimpleX"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Protocoles SimpleX audité par Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Mode incognito simplifié"; @@ -4180,9 +4646,15 @@ /* No comment provided by engineer. */ "SMP server" = "Serveur SMP"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "proxy SOCKS"; + /* blur media */ "Soft" = "Léger"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Certains paramètres de l'application n'ont pas été migrés."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Certains fichiers n'ont pas été exportés :"; @@ -4192,6 +4664,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "L'importation a entraîné des erreurs non fatales :"; +/* alert message */ +"Some servers failed the test:\n%@" = "Certains serveurs ont échoué le test :\n%@"; + /* notification title */ "Somebody" = "Quelqu'un"; @@ -4225,9 +4700,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Arrêter le chat"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Arrêter le chat pour permettre des actions sur la base de données"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Arrêtez le chat pour exporter, importer ou supprimer la base de données du chat. Vous ne pourrez pas recevoir et envoyer de messages pendant que le chat est arrêté."; @@ -4243,10 +4715,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Arrêter l'envoi du fichier ?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Cesser le partage"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Cesser le partage d'adresse ?"; /* authentication reason */ @@ -4276,18 +4748,30 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Supporter SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Passer de l'audio à la vidéo pendant l'appel."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Changer de profil de chat pour les invitations à usage unique."; + /* No comment provided by engineer. */ "System" = "Système"; /* No comment provided by engineer. */ "System authentication" = "Authentification du système"; +/* No comment provided by engineer. */ +"Tail" = "Queue"; + /* No comment provided by engineer. */ "Take picture" = "Prendre une photo"; /* 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."; @@ -4321,7 +4805,7 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; -/* No comment provided by engineer. */ +/* file error alert title */ "Temporary file error" = "Erreur de fichier temporaire"; /* server test failure */ @@ -4333,7 +4817,7 @@ /* No comment provided by engineer. */ "Test servers" = "Tester les serveurs"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Échec des tests !"; /* No comment provided by engineer. */ @@ -4346,10 +4830,10 @@ "Thanks to the users – contribute via Weblate!" = "Merci aux utilisateurs - contribuez via Weblate !"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "La 1ère plateforme sans aucun identifiant d'utilisateur – privée par design."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer."; /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer."; +"The app protects your privacy by using different operators in each conversation." = "L'application protège votre vie privée en utilisant des opérateurs différents pour chaque conversation."; /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "L'application demandera de confirmer les téléchargements à partir de serveurs de fichiers inconnus (sauf .onion)."; @@ -4360,6 +4844,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "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, your contact may be offline." = "La connexion a atteint la limite des messages non délivrés, votre contact est peut-être hors ligne."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "La connexion que vous avez acceptée sera annulée !"; @@ -4372,6 +4859,9 @@ /* 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."; @@ -4390,14 +4880,17 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Les messages seront marqués comme modérés pour tous les membres."; -/* No comment provided by engineer. */ -"The next generation of private messaging" = "La nouvelle génération de messagerie privée"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "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 profile is only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; +"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**."; + +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Le deuxième opérateur prédéfini de l'application !"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Le deuxième coche que nous avons manqué ! ✅"; @@ -4408,12 +4901,21 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Les serveurs pour les nouvelles connexions de votre profil de chat actuel **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Les serveurs pour les nouveaux fichiers de votre profil de chat actuel **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Le texte collé n'est pas un lien SimpleX."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "L'archive de la base de données envoyée sera définitivement supprimée des serveurs."; + /* No comment provided by engineer. */ "Themes" = "Thèmes"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Ces conditions s'appliquent également aux : **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ces paramètres s'appliquent à votre profil actuel **%@**."; @@ -4469,7 +4971,7 @@ "To ask any questions and to receive updates:" = "Si vous avez des questions et que vous souhaitez des réponses :"; /* No comment provided by engineer. */ -"To connect, your contact can scan QR code or use the link in the app." = "Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application."; +"To connect, your contact can scan QR code or use the link in the app." = "Pour se connecter, votre contact peut scanner un code QR ou utiliser un lien dans l'app."; /* No comment provided by engineer. */ "To hide unwanted messages." = "Pour cacher les messages indésirables."; @@ -4478,7 +4980,7 @@ "To make a new connection" = "Pour établir une nouvelle connexion"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts."; +"To protect against your link being replaced, you can compare contact security codes." = "Pour vous protéger contre le remplacement de votre lien, vous pouvez comparer les codes de sécurité des contacts."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Pour préserver le fuseau horaire, les fichiers image/voix utilisent le système UTC."; @@ -4489,15 +4991,33 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Pour protéger votre adresse IP, le routage privé utilise vos serveurs SMP pour délivrer les messages."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts."; + +/* No comment provided by engineer. */ +"To receive" = "Pour recevoir"; + +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Si vous souhaitez enregistrer une conversation, veuillez autoriser l'utilisation du microphone."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Si vous souhaitez enregistrer une vidéo, veuillez autoriser l'utilisation de la caméra."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Pour enregistrer un message vocal, veuillez accorder la permission d'utiliser le microphone."; /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Pour révéler votre profil caché, entrez le mot de passe dans le champ de recherche de la page **Vos profils de chat**."; +/* No comment provided by engineer. */ +"To send" = "Pour envoyer"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Pour prendre en charge les notifications push instantanées, la base de données du chat doit être migrée."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Pour utiliser les serveurs de **%@**, acceptez les conditions d'utilisation."; + /* 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."; @@ -4555,6 +5075,9 @@ /* rcv group event chat item */ "unblocked %@" = "%@ débloqué"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Messages non distribués"; + /* No comment provided by engineer. */ "Unexpected migration state" = "État de la migration inattendu"; @@ -4588,7 +5111,7 @@ /* No comment provided by engineer. */ "unknown servers" = "relais inconnus"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Serveurs inconnus !"; /* No comment provided by engineer. */ @@ -4612,10 +5135,7 @@ /* authentication reason */ "Unlock app" = "Déverrouiller l'app"; -/* No comment provided by engineer. */ -"unmute" = "démuter"; - -/* swipe action */ +/* notification label action */ "Unmute" = "Démute"; /* No comment provided by engineer. */ @@ -4672,12 +5192,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Utiliser les hôtes .onions"; +/* No comment provided by engineer. */ +"Use %@" = "Utiliser %@"; + /* No comment provided by engineer. */ "Use chat" = "Utiliser le chat"; /* No comment provided by engineer. */ "Use current profile" = "Utiliser le profil actuel"; +/* No comment provided by engineer. */ +"Use for files" = "Utiliser pour les fichiers"; + +/* No comment provided by engineer. */ +"Use for messages" = "Utiliser pour les messages"; + /* No comment provided by engineer. */ "Use for new connections" = "Utiliser pour les nouvelles connexions"; @@ -4702,9 +5231,15 @@ /* No comment provided by engineer. */ "Use server" = "Utiliser ce serveur"; +/* No comment provided by engineer. */ +"Use servers" = "Utiliser les serveurs"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Utiliser les serveurs SimpleX Chat ?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "Utiliser un proxy SOCKS"; + /* No comment provided by engineer. */ "Use the app while in the call." = "Utiliser l'application pendant l'appel."; @@ -4712,10 +5247,10 @@ "Use the app with one hand." = "Utiliser l'application d'une main."; /* No comment provided by engineer. */ -"User profile" = "Profil d'utilisateur"; +"User selection" = "Sélection de l'utilisateur"; /* No comment provided by engineer. */ -"User selection" = "Sélection de l'utilisateur"; +"Username" = "Nom d'utilisateur"; /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Vous utilisez les serveurs SimpleX."; @@ -4783,9 +5318,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Vidéos et fichiers jusqu'à 1Go"; +/* No comment provided by engineer. */ +"View conditions" = "Voir les conditions"; + /* No comment provided by engineer. */ "View security code" = "Afficher le code de sécurité"; +/* No comment provided by engineer. */ +"View updated conditions" = "Voir les conditions mises à jour"; + /* chat feature */ "Visible history" = "Historique visible"; @@ -4799,7 +5340,7 @@ "Voice messages are prohibited in this chat." = "Les messages vocaux sont interdits dans ce chat."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Les messages vocaux sont interdits dans ce groupe."; +"Voice messages are prohibited." = "Les messages vocaux sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Les messages vocaux ne sont pas autorisés"; @@ -4868,7 +5409,7 @@ "when IP hidden" = "lorsque l'IP est masquée"; /* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Vous pouvez accepter ou refuser les demandes de contacts."; +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Lorsque plusieurs opérateurs sont activés, aucun d'entre eux ne dispose de métadonnées permettant de savoir qui communique avec qui."; /* 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." = "Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite."; @@ -4894,7 +5435,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Sans Tor ou un VPN, votre adresse IP sera visible par les serveurs de fichiers."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@."; /* No comment provided by engineer. */ @@ -4918,9 +5459,6 @@ /* No comment provided by engineer. */ "you" = "vous"; -/* No comment provided by engineer. */ -"You" = "Vous"; - /* No comment provided by engineer. */ "You **must not** use the same database on two devices." = "Vous **ne devez pas** utiliser la même base de données sur deux appareils."; @@ -4936,6 +5474,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "Vous êtes déjà connecté·e à %@ via ce lien."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "Vous êtes déjà connecté avec %@."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "Vous êtes déjà en train de vous connecter à %@."; @@ -4981,6 +5522,9 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Vous pouvez choisir de le modifier dans les paramètres d'apparence."; +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Vous pouvez configurer les serveurs via les paramètres."; + /* No comment provided by engineer. */ "You can create it later" = "Vous pouvez la créer plus tard"; @@ -5005,6 +5549,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Vous pouvez envoyer des messages à %@ à partir des contacts archivés."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Vous pouvez définir un nom de connexion pour vous rappeler avec qui le lien a été partagé."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Vous pouvez configurer l'aperçu des notifications sur l'écran de verrouillage via les paramètres."; @@ -5014,9 +5561,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Vous pouvez partager cette adresse avec vos contacts pour leur permettre de se connecter avec **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Vous pouvez partager votre adresse sous la forme d'un lien ou d'un code QR - tout le monde peut l'utiliser pour vous contacter."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app"; @@ -5029,7 +5573,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Vous pouvez utiliser le format markdown pour mettre en forme les messages :"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion."; /* No comment provided by engineer. */ @@ -5048,10 +5592,10 @@ "you changed role of %@ to %@" = "vous avez modifié le rôle de %1$@ pour %2$@"; /* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** les messages de vos contacts."; +"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 could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer."; +"You decide who can connect." = "Vous choisissez qui peut se connecter."; /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Vous avez déjà demandé une connexion via cette adresse !"; @@ -5128,6 +5672,9 @@ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Vous continuerez à recevoir des appels et des notifications des profils mis en sourdine lorsqu'ils sont actifs."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Vous ne recevrez plus de messages de cette discussion. L'historique sera préservé."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Vous ne recevrez plus de messages de ce groupe. L'historique du chat sera conservé."; @@ -5143,9 +5690,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Vous utilisez un profil incognito pour ce groupe - pour éviter de partager votre profil principal ; inviter des contacts n'est pas possible"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Vos serveurs %@"; - /* No comment provided by engineer. */ "Your calls" = "Vos appels"; @@ -5155,9 +5699,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Votre base de données de chat n'est pas chiffrée - définisez une phrase secrète."; +/* alert title */ +"Your chat preferences" = "Vos préférences de discussion"; + /* No comment provided by engineer. */ "Your chat profiles" = "Vos profils de chat"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@)."; @@ -5167,6 +5717,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Vos contacts resteront connectés."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Vos informations d'identification peuvent être envoyées non chiffrées."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée."; @@ -5189,7 +5742,10 @@ "Your profile **%@** will be shared." = "Votre profil **%@** sera partagé."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts.\nLes serveurs SimpleX ne peuvent pas voir votre profil."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil."; + +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil."; @@ -5198,10 +5754,10 @@ "Your random profile" = "Votre profil aléatoire"; /* No comment provided by engineer. */ -"Your server" = "Votre serveur"; +"Your server address" = "Votre adresse de serveur"; /* No comment provided by engineer. */ -"Your server address" = "Votre adresse de serveur"; +"Your servers" = "Vos serveurs"; /* No comment provided by engineer. */ "Your settings" = "Vos paramètres"; @@ -5209,9 +5765,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Votre adresse SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Vos serveurs SMP"; - -/* No comment provided by engineer. */ -"Your XFTP servers" = "Vos serveurs XFTP"; - diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 8c70bcd626..5a9b6b4e38 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -1,18 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (másolható)"; @@ -23,29 +8,17 @@ "- 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." = "- kapcsolódás a [könyvtár szolgáltatáshoz](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- kézbesítési jelentések (legfeljebb 20 tag).\n- gyorsabb és stabilabb."; /* No comment provided by engineer. */ -"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilabb üzenetkézbesítés.\n- valamivel jobb csoportok.\n- és még sok más!"; +"- 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. */ -"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- opcionális értesítés a törölt kapcsolatokról.\n- profilnevek szóközökkel.\n- és még sok más!"; +"- 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!"; /* No comment provided by engineer. */ -"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- hangüzenetek legfeljebb 5 perces időtartamig.\n- egyedi eltűnési időhatár megadása.\n- előzmények szerkesztése."; - -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; +"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése."; /* No comment provided by engineer. */ "!1 colored!" = "!1 színezett!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(új)"; @@ -53,25 +26,19 @@ "(this device v%@)" = "(ez az eszköz: v%@)"; /* No comment provided by engineer. */ -")" = ")"; - -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Hozzájárulás](https://github.com/simplex-chat/simplex-chat#contribute)"; +"[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)" = "[Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat)"; +"[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. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; +"**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."; /* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása az ismerőse számára."; - -/* No comment provided by engineer. */ -"**Create group**: to create a new group." = "**Csoport létrehozása**: új csoport létrehozásához."; +"**Create group**: to create a new group." = "**Csoport létrehozása:** új csoport létrehozásához."; /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e titkosított** hanghívás"; @@ -80,25 +47,28 @@ "**e2e encrypted** video call" = "**e2e titkosított** videóhívás"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; +"**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 notifications server, check messages periodically in the background (depends 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." = "**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)."; /* 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 az ismerőseitől érkező üzenetek visszafejtését."; +"**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." = "**Figyelem**: NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt."; +"**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 notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; +"**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."; /* No comment provided by engineer. */ -"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés**: Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; +"**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. */ -"**Warning**: the archive will be removed." = "**Figyelem**: az archívum törlésre kerül."; +"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; + +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Figyelmeztetés:** az archívum el lesz távolítva."; /* No comment provided by engineer. */ "*bold*" = "\\*félkövér*"; @@ -110,10 +80,10 @@ "## History" = "## Előzmények"; /* copied message info */ -"## In reply to" = "## Válaszul erre:"; +"## In reply to" = "## Válaszul erre"; /* No comment provided by engineer. */ -"#secret#" = "#titkos#"; +"#secret#" = "#titok#"; /* No comment provided by engineer. */ "%@" = "%@"; @@ -137,7 +107,7 @@ "%@ and %@ connected" = "%@ és %@ kapcsolódott"; /* copied message info, at